# INTEGRATION NOTES:
# Buffyboard integrates as a virtual device in /dev/input
# which reads touch or pointer events from other input devices
# and generates events based on where those map to the keys it renders to the framebuffer.
#
# Buffyboard generates these events whether or not its onscreen keyboard is actually visible.
# Hence special care is needed if running anything which claims ownership of the display (such as a desktop environment),
# to avoid unwanted input events being triggered during normal desktop operation.
#
# Desktop users are recommended to either:
# 1. Stop buffyboard once your DE is started.
#   e.g. `services.buffyboard.unitConfig.Conflicts = [ "my-de.service" ];`
# 2. Configure your DE to ignore input events from buffyboard (product-id=0; vendor-id=0; name=buffyboard)
#   e.g. `echo 'input "0:0:buffyboard" events disabled' > ~/.config/sway/config`

{
  config,
  lib,
  pkgs,
  utils,
  ...
}:
let
  cfg = config.services.buffyboard;
  ini = pkgs.formats.ini { };
in
{
  meta.maintainers = with lib.maintainers; [ colinsane ];

  options = {
    services.buffyboard = with lib; {
      enable = mkEnableOption "buffyboard framebuffer keyboard (on-screen keyboard)";
      package = mkPackageOption pkgs "buffybox" { };

      extraFlags = mkOption {
        type = types.listOf types.str;
        default = [ ];
        description = ''
          Extra CLI arguments to pass to buffyboard.
        '';
        example = [
          "--geometry=1920x1080@640,0"
          "--dpi=192"
          "--rotate=2"
          "--verbose"
        ];
      };

      configFile = mkOption {
        type = lib.types.path;
        default = ini.generate "buffyboard.conf" (lib.filterAttrsRecursive (_: v: v != null) cfg.settings);
        defaultText = lib.literalExpression ''ini.generate "buffyboard.conf" cfg.settings'';
        description = ''
          Path to an INI format configuration file to provide Buffyboard.
          By default, this is generated from whatever you've set in `settings`.
          If specified manually, then `settings` is ignored.

          For an example config file see [here](https://gitlab.postmarketos.org/postmarketOS/buffybox/-/blob/master/buffyboard/buffyboard.conf)
        '';
      };

      settings = mkOption {
        description = ''
          Settings to include in /etc/buffyboard.conf.
          Every option here is strictly optional:
          Buffyboard will use its own baked-in defaults for those options left unset.
        '';
        type = types.submodule {
          freeformType = ini.type;

          options.keyboard.haptic_feedback = mkOption {
            type = types.nullOr types.bool;
            default = null;
            description = ''
              Enable or disable vibrations when pressing keys.
            '';
          };

          options.keyboard.sticky_shift = mkOption {
            type = types.nullOr types.bool;
            default = null;
            description = ''
              Changes shift key behavior. When true, the keyboard remains in uppercase mode until
              the shift key is pressed again (sticky). When false, the keyboard switches back to
              lowercase mode and the shift key deactivates after a non-modifier key is pressed.
            '';
          };

          options.input.pointer = mkOption {
            type = types.nullOr types.bool;
            default = null;
            description = ''
              Enable or disable the use of a hardware mouse or other pointing device.
            '';
          };
          options.input.touchscreen = mkOption {
            type = types.nullOr types.bool;
            default = null;
            description = ''
              Enable or disable the use of the touchscreen.
            '';
          };

          options.theme.default = mkOption {
            type = types.either types.str (
              types.enum [
                null
                "adwaita-dark"
                "breezy-dark"
                "breezy-light"
                "nord-dark"
                "nord-light"
                "pmos-dark"
                "pmos-light"
              ]
            );
            default = null;
            description = ''
              Selects the default theme on boot. Can be changed at runtime to the alternative theme.
            '';
          };
          options.quirks.fbdev_force_refresh = mkOption {
            type = types.nullOr types.bool;
            default = null;
            description = ''
              If true and using the framebuffer backend, this triggers a display refresh after every draw operation.
              This has a negative performance impact.
            '';
          };
          options.quirks.ignore_unused_terminals = mkOption {
            type = types.nullOr types.bool;
            default = null;
            description = ''
              If true, buffyboard won't automatically update the layout of a new terminal and
              draw the keyboard, if the terminal is not opened by any process. In this case
              SIGUSR1 should be sent to buffyboard to update the layout. This quirk was introduced
              to resolve a race between buffyboard and systemd-logind according to the following scenario:
              - A user switches to a new virtual terminal
              - Buffyboard opens the terminal and changes the number of rows
              - systemd-logind sees that the terminal is opened by some other process and don't start getty@.service

              The race is resolved by enabling this option and installing a drop-in file
              for getty@.service that sends SIGUSR1 to buffyboard.
            '';
          };
        };
        default = { };
      };
    };
  };

  config = lib.mkIf cfg.enable {
    systemd.packages = [ cfg.package ];
    systemd.services.buffyboard = {
      # upstream provides the service (including systemd hardening): we just configure it to start by default
      # and override ExecStart so as to optionally pass extra arguments
      serviceConfig.ExecStart = [
        "" # clear default ExecStart
        (utils.escapeSystemdExecArgs (
          [
            (lib.getExe' cfg.package "buffyboard")
            "--config-override"
            cfg.configFile
          ]
          ++ cfg.extraFlags
        ))
      ];
      wantedBy = [ "getty.target" ];
    };
  };
}
