{
  config,
  pkgs,
  lib,
  ...
}:
with lib;
let
  mkKeyValue =
    key: value:
    if value == true then
      # sets with a true boolean value are coerced to just the key name
      key
    else if value == false then
      # or omitted completely when false
      ""
    else
      (generators.mkKeyValueDefault { inherit mkValueString; } ": " key value);

  mkAttrsString = value: (generators.toKeyValue { inherit mkKeyValue; } value);

  mkValueString =
    value:
    if isList value then
      (concatStringsSep "," (map mkValueString value))
    else if isAttrs value then
      "\n" + (mkAttrsString value)
    else
      (generators.mkValueStringDefault { } value);

  mkSectionName = value: "[" + (escape [ "[" "]" ] value) + "]";

  mkSection = name: attrs: ''
    ${mkSectionName name}
    ${mkAttrsString attrs}
  '';

  mkVolume = name: attrs: ''
    ${mkSectionName name}
    ${attrs.path}
    ${mkAttrsString {
      accs = attrs.access;
      flags = attrs.flags;
    }}
  '';

  passwordPlaceholder = name: "{{password-${name}}}";

  accountsWithPlaceholders = mapAttrs (name: attrs: passwordPlaceholder name);

  volumesWithoutVariables = filterAttrs (k: v: !(hasInfix "\${" v.path)) cfg.volumes;

  configStr = ''
    ${mkSection "global" cfg.settings}
    ${cfg.globalExtraConfig}
    ${mkSection "accounts" (accountsWithPlaceholders cfg.accounts)}
    ${mkSection "groups" cfg.groups}
    ${concatStringsSep "\n" (mapAttrsToList mkVolume cfg.volumes)}
  '';

  cfg = config.services.copyparty;
  configFile = pkgs.writeText "copyparty.conf" configStr;
  runtimeConfigPath = "/run/copyparty/copyparty.conf";
  externalCacheDir = "/var/cache/copyparty";
  externalStateDir = "/var/lib/copyparty";
  defaultShareDir = "${externalStateDir}/data";
in
{
  options.services.copyparty = {
    enable = mkEnableOption "web-based file manager";

    package = mkPackageOption pkgs "copyparty" {
      extraDescription = ''
        Package of the application to run, exposed for overriding purposes.
      '';
    };

    mkHashWrapper = mkOption {
      type = types.bool;
      default = true;
      description = ''
        Make a shell script wrapper called {command}`copyparty-hash` with all options set here,
        that launches the hashing cli.
      '';
    };

    user = mkOption {
      type = types.str;
      default = "copyparty";
      description = ''
        The user that copyparty will run under.

        If changed from default, you are responsible for making sure the user exists.
      '';
    };

    group = mkOption {
      type = types.str;
      default = "copyparty";
      description = ''
        The group that copyparty will run under.

        If changed from default, you are responsible for making sure the user exists.
      '';
    };

    openFilesLimit = mkOption {
      default = 4096;
      type = types.either types.int types.str;
      description = "Number of files to allow copyparty to open.";
    };

    settings = mkOption {
      type = types.attrs;
      description = ''
        Global settings to apply.
        Directly maps to values in the `[global]` section of the copyparty config.
        Cannot set "c" or "hist", those are set by this module.
        See {command}`copyparty --help` for more details.
      '';
      default = {
        i = "127.0.0.1";
        no-reload = true;
        hist = externalCacheDir;
      };
      example = literalExpression ''
        {
          i = "0.0.0.0";
          no-reload = true;
          hist = ${externalCacheDir};
        }
      '';
    };

    globalExtraConfig = mkOption {
      type = types.str;
      default = "";
      description = "Appended to the end of the `[global]` section verbatim. This is useful for flags which are used in a repeating manner (e.g. `ipu: 255.255.255.1=user`) which can't be repeated in the settings = {} attribute set.";
    };

    accounts = mkOption {
      type = types.attrsOf (
        types.submodule (
          { ... }:
          {
            options = {
              passwordFile = mkOption {
                type = types.str;
                description = ''
                  Runtime file path to a file containing the user password.
                  Must be readable by the copyparty user.
                '';
                example = "/run/keys/copyparty/ed";
              };
            };
          }
        )
      );
      description = ''
        A set of copyparty accounts to create.
      '';
      default = { };
      example = literalExpression ''
        {
          ed.passwordFile = "/run/keys/copyparty/ed";
        };
      '';
    };

    groups = mkOption {
      type = types.attrsOf (types.listOf types.str);
      description = ''
        A set of copyparty groups to create and the users that should be part of each group.
      '';
      default = { };
      example = literalExpression ''
        {
          group_name = [ "user1" "user2" ];
        };
      '';
    };

    volumes = mkOption {
      type = types.attrsOf (
        types.submodule (
          { ... }:
          {
            options = {
              path = mkOption {
                type = types.path;
                description = ''
                  Path of a directory to share.
                '';
              };
              access = mkOption {
                type = types.attrs;
                description = ''
                  Attribute list of permissions and the users to apply them to.

                  The key must be a string containing any combination of allowed permission:
                  * "r" (read):   list folder contents, download files
                  * "w" (write):  upload files; need "r" to see the uploads
                  * "m" (move):   move files and folders; need "w" at destination
                  * "d" (delete): permanently delete files and folders
                  * "g" (get):    download files, but cannot see folder contents
                  * "G" (upget):  "get", but can see filekeys of their own uploads
                  * "h" (html):   "get", but folders return their index.html
                  * "a" (admin):  can see uploader IPs, config-reload

                  For example: "rwmd"

                  The value must be one of:
                  * an account name, defined in `accounts`
                  * a list of account names
                  * "*", which means "any account"
                '';
                example = literalExpression ''
                  {
                    # wG = write-upget = see your own uploads only
                    wG = "*";
                    # read-write-modify-delete for users "ed" and "k"
                    rwmd = ["ed" "k"];
                  };
                '';
              };
              flags = mkOption {
                type = types.attrs;
                description = ''
                  Attribute list of volume flags to apply.
                  See {command}`copyparty --help-flags` for more details.
                '';
                example = literalExpression ''
                  {
                    # "fk" enables filekeys (necessary for upget permission) (4 chars long)
                    fk = 4;
                    # scan for new files every 60sec
                    scan = 60;
                    # volflag "e2d" enables the uploads database
                    e2d = true;
                    # "d2t" disables multimedia parsers (in case the uploads are malicious)
                    d2t = true;
                    # skips hashing file contents if path matches *.iso
                    nohash = "\.iso$";
                  };
                '';
                default = { };
              };
            };
          }
        )
      );
      description = "A set of copyparty volumes to create";
      default = {
        "/" = {
          path = defaultShareDir;
          access = {
            r = "*";
          };
        };
      };
      example = literalExpression ''
        {
          "/" = {
            path = ${defaultShareDir};
            access = {
              # wG = write-upget = see your own uploads only
              wG = "*";
              # read-write-modify-delete for users "ed" and "k"
              rwmd = ["ed" "k"];
            };
          };
        };
      '';
    };
  };

  config = mkIf cfg.enable (
    let
      command = "${getExe cfg.package} -c ${runtimeConfigPath}";
    in
    {
      systemd.services.copyparty = {
        description = "http file sharing hub";
        wantedBy = [ "multi-user.target" ];

        environment = {
          PYTHONUNBUFFERED = "true";
          XDG_CONFIG_HOME = externalStateDir;
        };

        preStart =
          let
            replaceSecretCommand =
              name: attrs:
              "${getExe pkgs.replace-secret} '${passwordPlaceholder name}' '${attrs.passwordFile}' ${runtimeConfigPath}";
          in
          ''
            set -euo pipefail
            install -m 600 ${configFile} ${runtimeConfigPath}
            ${concatStringsSep "\n" (mapAttrsToList replaceSecretCommand cfg.accounts)}
          '';

        serviceConfig = {
          Type = "simple";
          ExecStart = command;
          # Hardening options
          User = cfg.user;
          Group = cfg.group;
          RuntimeDirectory = [ "copyparty" ];
          RuntimeDirectoryMode = "0700";
          StateDirectory = [ "copyparty" ];
          StateDirectoryMode = "0700";
          CacheDirectory = lib.mkIf (cfg.settings ? hist) [ "copyparty" ];
          CacheDirectoryMode = lib.mkIf (cfg.settings ? hist) "0700";
          WorkingDirectory = externalStateDir;
          BindReadOnlyPaths = [
            "/nix/store"
            "-/etc/resolv.conf"
            "-/etc/nsswitch.conf"
            "-/etc/group"
            "-/etc/hosts"
            "-/etc/localtime"
          ] ++ (mapAttrsToList (k: v: "-${v.passwordFile}") cfg.accounts);
          BindPaths =
            (if cfg.settings ? hist then [ cfg.settings.hist ] else [ ])
            ++ [ externalStateDir ]
            ++ (mapAttrsToList (k: v: v.path) volumesWithoutVariables);
          # ProtectSystem = "strict";
          # Note that unlike what 'ro' implies,
          # this actually makes it impossible to read anything in the root FS,
          # except for things explicitly mounted via `RuntimeDirectory`, `StateDirectory`, `CacheDirectory`, and `BindReadOnlyPaths`.
          # This is because TemporaryFileSystem creates a *new* *empty* filesystem for the process, so only bindmounts are visible.
          TemporaryFileSystem = "/:ro";
          PrivateTmp = true;
          PrivateDevices = true;
          ProtectKernelTunables = true;
          ProtectControlGroups = true;
          RestrictSUIDSGID = true;
          PrivateMounts = true;
          ProtectKernelModules = true;
          ProtectKernelLogs = true;
          ProtectHostname = true;
          ProtectClock = true;
          ProtectProc = "invisible";
          ProcSubset = "pid";
          RestrictNamespaces = true;
          RemoveIPC = true;
          UMask = "0077";
          LimitNOFILE = cfg.openFilesLimit;
          NoNewPrivileges = true;
          LockPersonality = true;
          RestrictRealtime = true;
          MemoryDenyWriteExecute = true;
        };
      };

      # ensure volumes exist:
      systemd.tmpfiles.settings."copyparty" = (
        lib.attrsets.mapAttrs' (
          name: value:
          lib.attrsets.nameValuePair (value.path) {
            d = {
              #: in front of things means it wont change it if the directory already exists.
              group = ":${cfg.group}";
              user = ":${cfg.user}";
              mode = ":${
                # Use volume permissions if set
                if (value.flags ? chmod_d) then
                  value.flags.chmod_d
                # Else, use global permission if set
                else if (cfg.settings ? chmod-d) then
                  cfg.settings.chmod-d
                # Else, use the default permission
                else
                  "755"
              }";
            };
          }
        ) volumesWithoutVariables
      );

      users.groups = lib.mkIf (cfg.group == "copyparty") {
        copyparty = { };
      };
      users.users = lib.mkIf (cfg.user == "copyparty") {
        copyparty = {
          description = "Service user for copyparty";
          group = cfg.group;
          home = externalStateDir;
          isSystemUser = true;
        };
      };
      environment.systemPackages = lib.mkIf cfg.mkHashWrapper [
        (pkgs.writeShellScriptBin "copyparty-hash" ''
          set -a  # automatically export variables
          # set same environment variables as the systemd service
          ${lib.pipe config.systemd.services.copyparty.environment [
            (lib.filterAttrs (n: v: v != null && n != "PATH"))
            (lib.mapAttrs (_: v: "${v}"))
            (lib.toShellVars)
          ]}
          PATH=${config.systemd.services.copyparty.environment.PATH}:$PATH

          exec ${command} --ah-cli
        '')
      ];
    }
  );
}
