{
  config,
  pkgs,
  lib,
  ...
}:
let
  inherit (lib)
    getExe
    isBool
    listToAttrs
    literalExpression
    maintainers
    mkEnableOption
    mkIf
    mkOption
    mkPackageOption
    optionalString
    replaceChars
    substring
    toLower
    types
    ;
  inherit (types)
    bool
    either
    listOf
    str
    submodule
    ;

  cfg = config.programs.pay-respects;

  settingsFormat = pkgs.formats.toml { };
  inherit (settingsFormat) generate type;

  finalPackage =
    if cfg.aiIntegration != true then
      (pkgs.runCommand "pay-respects-wrapper"
        {
          nativeBuildInputs = [ pkgs.makeBinaryWrapper ];
          inherit (cfg.package) meta;
        }
        ''
          mkdir -p $out/bin
          makeWrapper ${getExe cfg.package} $out/bin/${cfg.package.meta.mainProgram} \
            ${optionalString (cfg.aiIntegration == false) "--set _PR_AI_DISABLE true"}
            ${optionalString (cfg.aiIntegration != false) ''
              --set _PR_AI_URL ${cfg.aiIntegration.url} \
              --set _PR_AI_MODEL ${cfg.aiIntegration.model} \
              --set _PR_AI_LOCALE ${cfg.aiIntegration.locale}
            ''}
        ''
      )
    else
      cfg.package;

  initScript =
    shell:
    if (shell != "fish") then
      ''
        eval "$(${getExe finalPackage} ${shell} --alias ${cfg.alias})"
      ''
    else
      ''
        ${getExe finalPackage} ${shell} --alias ${cfg.alias} | source
      '';
in
{
  options = {
    programs.pay-respects = {
      enable = mkEnableOption "pay-respects, an app which corrects your previous console command";

      package = mkPackageOption pkgs "pay-respects" { };

      alias = mkOption {
        default = "f";
        type = str;
        description = ''
          `pay-respects` needs an alias to be configured.
          The default value is `f`, but you can use anything else as well.
        '';
      };
      runtimeRules = mkOption {
        type = listOf type;
        default = [ ];
        example = literalExpression ''
          [
            {
              command = "xl";
              match_err = [
                {
                  pattern = [
                    "Permission denied"
                  ];
                  suggest = [
                    '''
                      #[executable(sudo), !cmd_contains(sudo), err_contains(libxl: error:)]
                      sudo {{command}}
                    '''
                  ];
                }
              ];
            }
          ];
        '';
        description = ''
          List of rules to be added to `/etc/xdg/pay-respects/rules`.
          `pay-respects` will read the contents of these generated rules to recommend command corrections.
          Each rule module should start with the `command` attribute that specifies the command name. See the [upstream documentation](https://codeberg.org/iff/pay-respects/src/branch/main/rules.md) for more information.
        '';
      };
      aiIntegration = mkOption {
        default = false;
        example = {
          url = "http://127.0.0.1:11434/v1/chat/completions";
          model = "llama3";
          locale = "nl-be";
        };
        description = ''
          Whether to enable `pay-respects`' LLM integration. When there is no rule for a given error, `pay-respects` can query an OpenAI-compatible API endpoint for command corrections.

          - If this is set to `false`, all LLM-related features are disabled.
          - If this is set to `true`, the default OpenAI endpoint will be used, using upstream's API key. This default API key may be rate-limited.
          - You can also set a custom API endpoint, large language model and locale for command corrections. Simply access the `aiIntegration.url`, `aiIntegration.model` and `aiIntegration.locale` options, as described in the example.
            - Take a look at the [services.ollama](#opt-services.ollama.enable) NixOS module if you wish to host a local large language model for `pay-respects`.

          For all of these methods, you can set a custom secret API key by using the `_PR_AI_API_KEY` environment variable.
        '';
        type = either bool (submodule {
          options = {
            url = mkOption {
              default = "";
              example = "https://api.openai.com/v1/chat/completions";
              type = str;
              description = "The OpenAI-compatible API endpoint that `pay-respects` will query for command corrections.";
            };
            model = mkOption {
              default = "";
              example = "llama3";
              type = str;
              description = "The model used by `pay-respects` to generate command corrections.";
            };
            locale = mkOption {
              default = toLower (replaceChars [ "_" ] [ "-" ] (substring 0 5 config.i18n.defaultLocale));
              example = "nl-be";
              type = str;
              description = ''
                The locale to be used for LLM responses.
                The accepted format is a lowercase [`ISO 639-1` language code](https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes), followed by a dash '-', followed by a lowercase [`ISO 3166-1 alpha-2` country code](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2).
              '';
            };
          };
        });
      };
    };
  };

  config = mkIf cfg.enable {
    assertions =
      map
        (attr: {
          assertion = (!isBool cfg.aiIntegration) -> (cfg.aiIntegration.${attr} != "");
          message = ''
            programs.pay-respects.aiIntegration is configured as a submodule, but you have not configured a value for programs.pay-respects.aiIntegration.${attr}!
          '';
        })
        [
          "url"
          "model"
        ];

    environment = {
      etc = listToAttrs (
        map (rule: {
          name = "xdg/pay-respects/rules/${rule.command}.toml";
          value = {
            source = generate "${rule.command}.toml" rule;
          };
        }) cfg.runtimeRules
      );

      systemPackages = [ finalPackage ];
    };

    programs = {
      bash.interactiveShellInit = initScript "bash";
      fish.interactiveShellInit = optionalString config.programs.fish.enable (initScript "fish");
      zsh.interactiveShellInit = optionalString config.programs.zsh.enable (initScript "zsh");
    };
  };
  meta.maintainers = with maintainers; [ sigmasquadron ];
}
