{ config, lib, ... }:
let
  inherit (lib) mkIf mkOption types;

  cfg = config.programs.readline;

  mkSetVariableStr =
    n: v:
    let
      mkValueStr =
        v:
        if v == true then
          "on"
        else if v == false then
          "off"
        else if lib.isInt v then
          toString v
        else if lib.isString v then
          v
        else
          abort "values ${lib.toPretty v} is of unsupported type";
    in
    "set ${n} ${mkValueStr v}";

  mkBindingStr =
    k: v:
    let
      isKeynameNotKeyseq =
        k:
        builtins.elem (builtins.head (lib.splitString "-" (lib.toLower k))) [
          "control"
          "meta"
        ];
    in
    if isKeynameNotKeyseq k then "${k}: ${v}" else ''"${k}": ${v}'';

in
{
  options.programs.readline = {
    enable = lib.mkEnableOption "readline";

    bindings = mkOption {
      default = { };
      type = types.attrsOf types.str;
      example = lib.literalExpression ''
        { "\\C-h" = "backward-kill-word"; }
      '';
      description = "Readline bindings.";
    };

    variables = mkOption {
      type = with types; attrsOf (either str (either int bool));
      default = { };
      example = {
        expand-tilde = true;
      };
      description = ''
        Readline customization variable assignments.
      '';
    };

    includeSystemConfig = mkOption {
      type = types.bool;
      default = true;
      description = "Whether to include the system-wide configuration.";
    };

    extraConfig = mkOption {
      type = types.lines;
      default = "";
      description = ''
        Configuration lines appended unchanged to the end of the
        {file}`~/.inputrc` file.
      '';
    };
  };

  config = mkIf cfg.enable (
    let
      finalConfig =
        let
          configStr = lib.concatStringsSep "\n" (
            lib.optional cfg.includeSystemConfig "$include /etc/inputrc"
            ++ lib.mapAttrsToList mkSetVariableStr cfg.variables
            ++ lib.mapAttrsToList mkBindingStr cfg.bindings
          );
        in
        ''
          # Generated by Home Manager.

          ${configStr}
          ${cfg.extraConfig}
        '';
    in
    lib.mkMerge [
      (mkIf (!config.home.preferXdgDirectories) {
        home.file.".inputrc".text = finalConfig;
      })
      (mkIf config.home.preferXdgDirectories {
        xdg.configFile.inputrc.text = finalConfig;
        home.sessionVariables.INPUTRC = "${config.xdg.configHome}/inputrc";
      })
    ]
  );
}
