{ config, pkgs, lib, ... }:
with lib;
let
  cfg = config.services.snapper;
in
{
  options.services.snapper = {
    snapshotInterval = mkOption {
      type = types.str;
      default = "hourly";
      description = ''
        Snapshot interval.
        The format is described in
        systemd.time
        7.
      '';
    };
    cleanupInterval = mkOption {
      type = types.str;
      default = "1d";
      description = ''
        Cleanup interval.
        The format is described in
        systemd.time
        7.
      '';
    };
    filters = mkOption {
      type = types.nullOr types.lines;
      default = null;
      description = ''
        Global display difference filter. See man:snapper(8) for more details.
      '';
    };
    configs = mkOption {
      default = { };
      example = literalExample {
        home = {
          subvolume = "/home";
          extraConfig = ''
            ALLOW_USERS="alice"
          '';
        };
      };
      description = ''
        Subvolume configuration
      '';
      type = types.attrsOf (types.submodule {
        options = {
          subvolume = mkOption {
            type = types.path;
            description = ''
              Path of the subvolume or mount point.
              This path is a subvolume and has to contain a subvolume named
              .snapshots.
              See also man:snapper(8) section PERMISSIONS.
            '';
          };
          fstype = mkOption {
            type = types.enum [ "btrfs" ];
            default = "btrfs";
            description = ''
              Filesystem type. Only btrfs is stable and tested.
            '';
          };
          extraConfig = mkOption {
            type = types.lines;
            default = "";
            description = ''
              Additional configuration next to SUBVOLUME and FSTYPE.
              See man:snapper-configs(5).
            '';
          };
        };
      });
    };
  };
  config = mkIf (cfg.configs != {}) (let
    documentation = [ "man:snapper(8)" "man:snapper-configs(5)" ];
  in {
    environment = {
      systemPackages = [ pkgs.snapper ];
      # Note: snapper/config-templates/default is only needed for create-config
      #       which is not the NixOS way to configure.
      etc = {
        "sysconfig/snapper".text = ''
          SNAPPER_CONFIGS="${lib.concatStringsSep " " (builtins.attrNames cfg.configs)}"
        '';
      }
      // (mapAttrs' (name: subvolume: nameValuePair "snapper/configs/${name}" ({
        text = ''
          ${subvolume.extraConfig}
          FSTYPE="${subvolume.fstype}"
          SUBVOLUME="${subvolume.subvolume}"
        '';
      })) cfg.configs)
      // (lib.optionalAttrs (cfg.filters != null) {
        "snapper/filters/default.txt".text = cfg.filters;
      });
    };
    services.dbus.packages = [ pkgs.snapper ];
    systemd.services.snapper-timeline = {
      description = "Timeline of Snapper Snapshots";
      inherit documentation;
      serviceConfig.ExecStart = "${pkgs.snapper}/lib/snapper/systemd-helper --timeline";
    };
    systemd.timers.snapper-timeline = {
      description = "Timeline of Snapper Snapshots";
      inherit documentation;
      wantedBy = [ "basic.target" ];
      timerConfig.OnCalendar = cfg.snapshotInterval;
    };
    systemd.services.snapper-cleanup = {
      description = "Cleanup of Snapper Snapshots";
      inherit documentation;
      serviceConfig.ExecStart = "${pkgs.snapper}/lib/snapper/systemd-helper --cleanup";
    };
    systemd.timers.snapper-cleanup = {
      description = "Cleanup of Snapper Snapshots";
      inherit documentation;
      wantedBy = [ "basic.target" ];
      timerConfig.OnBootSec = "10m";
      timerConfig.OnUnitActiveSec = cfg.cleanupInterval;
    };
  });
}