{ config, lib, pkgs, ... }:
with lib;
let
  cfg = config.services.watchdogd;

  mkPluginOpts = plugin: defWarn: defCrit: {
    enabled = mkEnableOption "watchdogd plugin ${plugin}";
    interval = mkOption {
      type = types.ints.unsigned;
      default = 300;
      description = ''
        Amount of seconds between every poll.
      '';
    };
    logmark = mkOption {
      type = types.bool;
      default = false;
      description = ''
        Whether to log current stats every poll interval.
      '';
    };
    warning = mkOption {
      type = types.numbers.nonnegative;
      default = defWarn;
      description = ''
        The high watermark level. Alert sent to log.
      '';
    };
    critical = mkOption {
      type = types.numbers.nonnegative;
      default = defCrit;
      description = ''
        The critical watermark level. Alert sent to log, followed by reboot or script action.
      '';
    };
  };
in {
  options.services.watchdogd = {
    enable = mkEnableOption "watchdogd, an advanced system & process supervisor";
    package = mkPackageOption pkgs "watchdogd" { };

    settings = mkOption {
      type = with types; submodule {
        freeformType = let
          valueType = oneOf [
            bool
            int
            float
            str
          ];
        in attrsOf (either valueType (attrsOf valueType));

        options = {
          timeout = mkOption {
            type = types.ints.unsigned;
            default = 15;
            description = ''
              The WDT timeout before reset.
            '';
          };
          interval = mkOption {
            type = types.ints.unsigned;
            default = 5;
            description = ''
              The kick interval, i.e. how often {manpage}`watchdogd(8)` should reset the WDT timer.
            '';
          };

          safe-exit = mkOption {
            type = types.bool;
            default = true;
            description = ''
              With {var}`safeExit` enabled, the daemon will ask the driver to disable the WDT before exiting.
              However, some WDT drivers (or hardware) may not support this.
            '';
          };

          filenr = mkPluginOpts "filenr" 0.9 1.0;

          loadavg = mkPluginOpts "loadavg" 1.0 2.0;

          meminfo = mkPluginOpts "meminfo" 0.9 0.95;
        };
      };
      default = { };
      description = ''
        Configuration to put in {file}`watchdogd.conf`.
        See {manpage}`watchdogd.conf(5)` for more details.
      '';
    };
  };

  config = let
    toConfig = attrs: concatStringsSep "\n" (mapAttrsToList toValue attrs);

    toValue = name: value:
      if isAttrs value
        then pipe value [
          (mapAttrsToList toValue)
          (map (s: "  ${s}"))
          (concatStringsSep "\n")
          (s: "${name} {\n${s}\n}")
        ]
      else if isBool value
        then "${name} = ${boolToString value}"
      else if any (f: f value) [isString isInt isFloat]
        then "${name} = ${toString value}"
      else throw ''
        Found invalid type in `services.watchdogd.settings`: '${typeOf value}'
      '';

    watchdogdConf = pkgs.writeText "watchdogd.conf" (toConfig cfg.settings);
  in mkIf cfg.enable {
    environment.systemPackages = [ cfg.package ];

    systemd.services.watchdogd = {
      documentation = [
        "man:watchdogd(8)"
        "man:watchdogd.conf(5)"
      ];
      wantedBy = [ "multi-user.target" ];
      description = "Advanced system & process supervisor";
      serviceConfig = {
        Type = "simple";
        ExecStart = "${cfg.package}/bin/watchdogd -n -f ${watchdogdConf}";
      };
    };
  };

  meta.maintainers = with maintainers; [ vifino ];
}
