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

  # https://github.com/nix-community/home-manager/blob/master/modules/programs/fish.nix
  abbrModule = types.submodule {
    options = {
      expansion = mkOption {
        type = with types; nullOr str;
        default = null;
        description = "The command expanded by an abbreviation.";
      };

      position = mkOption {
        type = with types; nullOr str;
        default = null;
        example = "anywhere";
        description = ''
          If the position is "command", the abbreviation expands only if the
          position is a command. If it is "anywhere", the abbreviation expands
          anywhere.
        '';
      };

      regex = mkOption {
        type = with types; nullOr str;
        default = null;
        description = ''
          The regular expression pattern matched instead of the literal name.
        '';
      };

      setCursor = mkOption {
        type = with types; (either bool str);
        default = false;
        description = ''
          The marker indicates the position of the cursor when the abbreviation
          is expanded. When setCursor is true, the marker is set with a default
          value of "%".
        '';
      };

      function = mkOption {
        type = with types; nullOr str;
        default = null;
        description = "The fish function expanded instead of a literal string.";
      };
    };
  };

  removeFishOnly = attrsets.filterAttrs (
    _: v:
    if (builtins.isAttrs v) then
      !(
        (v ? regex) || (v ? setCursor) || (v ? function) || ((v ? position) && (v.position == "anywhere"))
      )
    else
      true
  );

  abbrToAlias = attrsets.mapAttrs (
    _: v: if (builtins.isAttrs v) then v.expansion else v
  ) removeFishOnly;
in
{
  options = {
    # https://github.com/nix-community/home-manager/blob/master/modules/programs/fish.nix
    home.shellAbbrs = mkOption {
      type = with types; attrsOf (either str abbrModule);
      default = { };
      example = literalExpression ''
        {
          l = "less";
          gco = "git checkout";
          "-C" = {
            position = "anywhere";
            expansion = "--color";
          };
        }
      '';
      description = ''
        An attribute set that maps aliases (the top level attribute names in
        this option) to abbreviations. Abbreviations are expanded with the
        longer phrase after they are entered.
      '';
    };
  };

  config =
    let
      fishCfg = config.programs.fish;
      inherit (config.home) shellAbbrs;
    in
    {
      programs.fish.shellAbbrs = mkIf fishCfg.enable shellAbbrs;

      home.shellAliases = mkIf (!fishCfg.enable) (abbrToAlias shellAbbrs);
    };
}
