{
  config,
  lib,
  pkgs,
  ...
}:

let
  cfg = config.services.system76-scheduler;

  inherit (builtins)
    concatStringsSep
    map
    toString
    attrNames
    ;
  inherit (lib)
    boolToString
    types
    mkOption
    literalExpression
    optional
    mkIf
    mkMerge
    ;
  inherit (types)
    nullOr
    listOf
    bool
    int
    ints
    float
    str
    enum
    ;

  withDefaults =
    optionSpecs: defaults:
    lib.genAttrs (attrNames optionSpecs) (
      name:
      mkOption (
        optionSpecs.${name}
        // {
          default = optionSpecs.${name}.default or defaults.${name} or null;
        }
      )
    );

  latencyProfile = withDefaults {
    latency = {
      type = int;
      description = "`sched_latency_ns`.";
    };
    nr-latency = {
      type = int;
      description = "`sched_nr_latency`.";
    };
    wakeup-granularity = {
      type = float;
      description = "`sched_wakeup_granularity_ns`.";
    };
    bandwidth-size = {
      type = int;
      description = "`sched_cfs_bandwidth_slice_us`.";
    };
    preempt = {
      type = enum [
        "none"
        "voluntary"
        "full"
      ];
      description = "Preemption mode.";
    };
  };
  schedulerProfile = withDefaults {
    nice = {
      type = nullOr (ints.between (-20) 19);
      description = "Niceness.";
    };
    class = {
      type = nullOr (enum [
        "idle"
        "batch"
        "other"
        "rr"
        "fifo"
      ]);
      example = literalExpression "\"batch\"";
      description = "CPU scheduler class.";
    };
    prio = {
      type = nullOr (ints.between 1 99);
      example = literalExpression "49";
      description = "CPU scheduler priority.";
    };
    ioClass = {
      type = nullOr (enum [
        "idle"
        "best-effort"
        "realtime"
      ]);
      example = literalExpression "\"best-effort\"";
      description = "IO scheduler class.";
    };
    ioPrio = {
      type = nullOr (ints.between 0 7);
      example = literalExpression "4";
      description = "IO scheduler priority.";
    };
    matchers = {
      type = nullOr (listOf str);
      default = [ ];
      example = literalExpression ''
        [
          "include cgroup=\"/user.slice/*.service\" parent=\"systemd\""
          "emacs"
        ]
      '';
      description = "Process matchers.";
    };
  };

  cfsProfileToString =
    name:
    let
      p = cfg.settings.cfsProfiles.${name};
    in
    "${name} latency=${toString p.latency} nr-latency=${toString p.nr-latency} wakeup-granularity=${toString p.wakeup-granularity} bandwidth-size=${toString p.bandwidth-size} preempt=\"${p.preempt}\"";

  prioToString = class: prio: if prio == null then "\"${class}\"" else "(${class})${toString prio}";

  schedulerProfileToString =
    name: a: indent:
    concatStringsSep " " (
      [ "${indent}${name}" ]
      ++ (optional (a.nice != null) "nice=${toString a.nice}")
      ++ (optional (a.class != null) "sched=${prioToString a.class a.prio}")
      ++ (optional (a.ioClass != null) "io=${prioToString a.ioClass a.ioPrio}")
      ++ (optional ((builtins.length a.matchers) != 0)
        "{\n${concatStringsSep "\n" (map (m: "  ${indent}${m}") a.matchers)}\n${indent}}"
      )
    );

in
{
  options = {
    services.system76-scheduler = {
      enable = lib.mkEnableOption "system76-scheduler";

      package = mkOption {
        type = types.package;
        default = pkgs.system76-scheduler;
        defaultText = literalExpression "pkgs.system76-scheduler";
        description = "Which System76-Scheduler package to use.";
      };

      useStockConfig = mkOption {
        type = bool;
        default = true;
        description = ''
          Use the (reasonable and featureful) stock configuration.

          When this option is `true`, `services.system76-scheduler.settings`
          are ignored.
        '';
      };

      settings = {
        cfsProfiles = {
          enable = mkOption {
            type = bool;
            default = true;
            description = "Tweak CFS latency parameters when going on/off battery";
          };

          default = latencyProfile {
            latency = 6;
            nr-latency = 8;
            wakeup-granularity = 1.0;
            bandwidth-size = 5;
            preempt = "voluntary";
          };
          responsive = latencyProfile {
            latency = 4;
            nr-latency = 10;
            wakeup-granularity = 0.5;
            bandwidth-size = 3;
            preempt = "full";
          };
        };

        processScheduler = {
          enable = mkOption {
            type = bool;
            default = true;
            description = "Tweak scheduling of individual processes in real time.";
          };

          useExecsnoop = mkOption {
            type = bool;
            default = true;
            description = "Use execsnoop (otherwise poll the precess list periodically).";
          };

          refreshInterval = mkOption {
            type = int;
            default = 60;
            description = "Process list poll interval, in seconds";
          };

          foregroundBoost = {
            enable = mkOption {
              type = bool;
              default = true;
              description = ''
                Boost foreground process priorities.

                (And de-boost background ones).  Note that this option needs cooperation
                from the desktop environment to work.  On Gnome the client side is
                implemented by the "System76 Scheduler" shell extension.
              '';
            };
            foreground = schedulerProfile {
              nice = 0;
              ioClass = "best-effort";
              ioPrio = 0;
            };
            background = schedulerProfile {
              nice = 6;
              ioClass = "idle";
            };
          };

          pipewireBoost = {
            enable = mkOption {
              type = bool;
              default = true;
              description = "Boost Pipewire client priorities.";
            };
            profile = schedulerProfile {
              nice = -6;
              ioClass = "best-effort";
              ioPrio = 0;
            };
          };
        };
      };

      assignments = mkOption {
        type = types.attrsOf (
          types.submodule {
            options = schedulerProfile { };
          }
        );
        default = { };
        example = literalExpression ''
          {
            nix-builds = {
              nice = 15;
              class = "batch";
              ioClass = "idle";
              matchers = [
                "nix-daemon"
              ];
            };
          }
        '';
        description = "Process profile assignments.";
      };

      exceptions = mkOption {
        type = types.listOf str;
        default = [ ];
        example = literalExpression ''
          [
            "include descends=\"schedtool\""
            "schedtool"
          ]
        '';
        description = "Processes that are left alone.";
      };
    };
  };

  config = mkIf cfg.enable {
    environment.systemPackages = [ cfg.package ];
    services.dbus.packages = [ cfg.package ];

    systemd.services.system76-scheduler = {
      description = "Manage process priorities and CFS scheduler latencies for improved responsiveness on the desktop";
      wantedBy = [ "multi-user.target" ];
      path = [
        # execsnoop needs those to extract kernel headers:
        pkgs.kmod
        pkgs.gnutar
        pkgs.xz
      ];
      serviceConfig = {
        Type = "dbus";
        BusName = "com.system76.Scheduler";
        ExecStart = "${cfg.package}/bin/system76-scheduler daemon";
        ExecReload = "${cfg.package}/bin/system76-scheduler daemon reload";
      };
    };

    environment.etc = mkMerge [
      (mkIf cfg.useStockConfig {
        # No custom settings: just use stock configuration with a fix for Pipewire
        "system76-scheduler/config.kdl".source = "${cfg.package}/data/config.kdl";
        "system76-scheduler/process-scheduler/00-dist.kdl".source = "${cfg.package}/data/pop_os.kdl";
        "system76-scheduler/process-scheduler/01-fix-pipewire-paths.kdl".source =
          ../../../../pkgs/by-name/sy/system76-scheduler/01-fix-pipewire-paths.kdl;
      })

      (
        let
          settings = cfg.settings;
          cfsp = settings.cfsProfiles;
          ps = settings.processScheduler;
        in
        mkIf (!cfg.useStockConfig) {
          "system76-scheduler/config.kdl".text = ''
            version "2.0"
            autogroup-enabled false
            cfs-profiles enable=${boolToString cfsp.enable} {
              ${cfsProfileToString "default"}
              ${cfsProfileToString "responsive"}
            }
            process-scheduler enable=${boolToString ps.enable} {
              execsnoop ${boolToString ps.useExecsnoop}
              refresh-rate ${toString ps.refreshInterval}
              assignments {
                ${
                  if ps.foregroundBoost.enable then
                    (schedulerProfileToString "foreground" ps.foregroundBoost.foreground "    ")
                  else
                    ""
                }
                ${
                  if ps.foregroundBoost.enable then
                    (schedulerProfileToString "background" ps.foregroundBoost.background "    ")
                  else
                    ""
                }
                ${
                  if ps.pipewireBoost.enable then
                    (schedulerProfileToString "pipewire" ps.pipewireBoost.profile "    ")
                  else
                    ""
                }
              }
            }
          '';
        }
      )

      {
        "system76-scheduler/process-scheduler/02-config.kdl".text =
          "exceptions {\n${concatStringsSep "\n" (map (e: "  ${e}") cfg.exceptions)}\n}\n"
          + "assignments {\n"
          + (concatStringsSep "\n" (
            map (name: schedulerProfileToString name cfg.assignments.${name} "  ") (attrNames cfg.assignments)
          ))
          + "\n}\n";
      }
    ];
  };

  meta = {
    maintainers = [ lib.maintainers.cmm ];
  };
}
