{
  config,
  pkgs,
  lib,
  ...
}:
let
  inherit (lib)
    mkOption
    optionalString
    strings
    types
    ;

  cfg = config.programs.zsh.prezto;

  relToDotDir =
    file:
    (optionalString (config.programs.zsh.dotDir != null) (config.programs.zsh.dotDir + "/")) + file;

  preztoModule = types.submodule {
    options = {
      enable = lib.mkEnableOption "prezto";

      package = lib.mkPackageOption pkgs "prezto" { default = "zsh-prezto"; };

      caseSensitive = mkOption {
        type = types.nullOr types.bool;
        # See <https://github.com/nix-community/home-manager/issues/2255>.
        default = true;
        example = true;
        description = "Set case-sensitivity for completion, history lookup, etc.";
      };

      color = mkOption {
        type = types.nullOr types.bool;
        default = true;
        example = false;
        description = ''
          Color output (automatically set to `false` on dumb terminals).
        '';
      };

      pmoduleDirs = mkOption {
        type = types.listOf types.path;
        default = [ ];
        example = lib.literalExpression ''[ "''${config.home.homeDirectory}/.zprezto-contrib" ]'';
        description = "Add additional directories to load prezto modules from.";
      };

      extraConfig = mkOption {
        type = types.lines;
        default = "";
        description = ''
          Additional configuration to add to {file}`.zpreztorc`.
        '';
      };

      extraModules = mkOption {
        type = types.listOf types.str;
        default = [ ];
        example = [
          "attr"
          "stat"
        ];
        description = ''
          Set the Zsh modules to load ({manpage}`zshmodules(1)`).
        '';
      };

      extraFunctions = mkOption {
        type = types.listOf types.str;
        default = [ ];
        example = [
          "zargs"
          "zmv"
        ];
        description = ''
          Set the Zsh functions to load ({manpage}`zshcontrib(1)`).
        '';
      };

      pmodules = mkOption {
        type = types.listOf types.str;
        default = [
          "environment"
          "terminal"
          "editor"
          "history"
          "directory"
          "spectrum"
          "utility"
          "completion"
          "prompt"
        ];
        description = "Set the Prezto modules to load (browse modules). The order matters.";
      };

      autosuggestions.color = mkOption {
        type = types.nullOr types.str;
        default = null;
        example = "fg=blue";
        description = "Set the query found color.";
      };

      completions.ignoredHosts = mkOption {
        type = types.listOf types.str;
        default = [ ];
        example = [
          "0.0.0.0"
          "127.0.0.1"
        ];
        description = ''
          Set the entries to ignore in static {file}`/etc/hosts` for
          host completion.
        '';
      };

      editor = {
        keymap = mkOption {
          type = types.nullOr (
            types.enum [
              "emacs"
              "vi"
            ]
          );
          default = "emacs";
          example = "vi";
          description = ''
            Set the key mapping style to `emacs` or `vi`.
          '';
        };

        dotExpansion = mkOption {
          type = types.nullOr types.bool;
          default = null;
          example = true;
          description = ''
            Automatically convert `....` to `../..`
          '';
        };

        promptContext = mkOption {
          type = types.nullOr types.bool;
          default = null;
          example = true;
          description = "Allow the Zsh prompt context to be shown.";
        };
      };

      git.submoduleIgnore = mkOption {
        type = types.nullOr (
          types.enum [
            "dirty"
            "untracked"
            "all"
            "none"
          ]
        );
        default = null;
        example = "all";
        description = ''
          Ignore submodules when they are `dirty`, `untracked`, `all`,
          or `none`.
        '';
      };

      gnuUtility.prefix = mkOption {
        type = types.nullOr types.str;
        default = null;
        example = "g";
        description = "Set the command prefix on non-GNU systems.";
      };

      historySubstring = {
        foundColor = mkOption {
          type = types.nullOr types.str;
          default = null;
          example = "fg=blue";
          description = "Set the query found color.";
        };

        notFoundColor = mkOption {
          type = types.nullOr types.str;
          default = null;
          example = "fg=red";
          description = "Set the query not found color.";
        };

        globbingFlags = mkOption {
          type = types.nullOr types.str;
          default = null;
          description = "Set the search globbing flags.";
        };
      };

      macOS.dashKeyword = mkOption {
        type = types.nullOr types.str;
        default = null;
        example = "manpages";
        description = ''
          Set the keyword used by {command}`mand` to open man pages
          in Dash.app.
        '';
      };

      prompt = {
        theme = mkOption {
          type = types.nullOr types.str;
          default = "sorin";
          example = "pure";
          description = ''
            Set the prompt theme to load. Setting it to `random`
            loads a random theme. Automatically set to `off` on dumb
            terminals.
          '';
        };

        pwdLength = mkOption {
          type = types.nullOr (
            types.enum [
              "short"
              "long"
              "full"
            ]
          );
          default = null;
          example = "short";
          description = ''
            Set the working directory prompt display length. By
            default, it is set to `short`. Set it to `long` (without `~`
            expansion) for longer or `full` (with `~` expansion) for
            even longer prompt display.
          '';
        };

        showReturnVal = mkOption {
          type = types.nullOr types.bool;
          default = null;
          example = true;
          description = ''
            Set the prompt to display the return code along with an
            indicator for non-zero return codes. This is not supported by all prompts.
          '';
        };
      };

      python = {
        virtualenvAutoSwitch = mkOption {
          type = types.nullOr types.bool;
          default = null;
          example = true;
          description = "Auto switch to Python virtualenv on directory change.";
        };

        virtualenvInitialize = mkOption {
          type = types.nullOr types.bool;
          default = null;
          example = true;
          description = "Automatically initialize virtualenvwrapper if pre-requisites are met.";
        };
      };

      ruby.chrubyAutoSwitch = mkOption {
        type = types.nullOr types.bool;
        default = null;
        example = true;
        description = "Auto switch the Ruby version on directory change.";
      };

      screen = {
        autoStartLocal = mkOption {
          type = types.nullOr types.bool;
          default = null;
          example = true;
          description = "Auto start a session when Zsh is launched in a local terminal.";
        };

        autoStartRemote = mkOption {
          type = types.nullOr types.bool;
          default = null;
          example = true;
          description = "Auto start a session when Zsh is launched in a SSH connection.";
        };
      };

      ssh.identities = mkOption {
        type = types.listOf types.str;
        default = [ ];
        example = [
          "id_rsa"
          "id_rsa2"
          "id_github"
        ];
        description = "Set the SSH identities to load into the agent.";
      };

      syntaxHighlighting = {
        highlighters = mkOption {
          type = types.listOf types.str;
          default = [ ];
          example = [
            "main"
            "brackets"
            "pattern"
            "line"
            "cursor"
            "root"
          ];
          description = ''
            Set syntax highlighters. By default, only the main
            highlighter is enabled.
          '';
        };

        styles = mkOption {
          type = types.attrsOf types.str;
          default = { };
          example = {
            builtin = "bg=blue";
            command = "bg=blue";
            function = "bg=blue";
          };
          description = "Set syntax highlighting styles.";
        };

        pattern = mkOption {
          type = types.attrsOf types.str;
          default = { };
          example = {
            "rm*-rf*" = "fg=white,bold,bg=red";
          };
          description = "Set syntax pattern styles.";
        };
      };

      terminal = {
        autoTitle = mkOption {
          type = types.nullOr types.bool;
          default = null;
          example = true;
          description = "Auto set the tab and window titles.";
        };

        windowTitleFormat = mkOption {
          type = types.nullOr types.str;
          default = null;
          example = "%n@%m: %s";
          description = "Set the window title format.";
        };

        tabTitleFormat = mkOption {
          type = types.nullOr types.str;
          default = null;
          example = "%m: %s";
          description = "Set the tab title format.";
        };

        multiplexerTitleFormat = mkOption {
          type = types.nullOr types.str;
          default = null;
          example = "%s";
          description = "Set the multiplexer title format.";
        };
      };

      tmux = {
        autoStartLocal = mkOption {
          type = types.nullOr types.bool;
          default = null;
          example = true;
          description = "Auto start a session when Zsh is launched in a local terminal.";
        };

        autoStartRemote = mkOption {
          type = types.nullOr types.bool;
          default = null;
          example = true;
          description = "Auto start a session when Zsh is launched in a SSH connection.";
        };

        itermIntegration = mkOption {
          type = types.nullOr types.bool;
          default = null;
          example = true;
          description = "Integrate with iTerm2.";
        };

        defaultSessionName = mkOption {
          type = types.nullOr types.str;
          default = null;
          example = "YOUR DEFAULT SESSION NAME";
          description = "Set the default session name.";
        };
      };

      utility.safeOps = mkOption {
        type = types.nullOr types.bool;
        default = null;
        example = true;
        description = ''
          Enabled safe options. This aliases {command}`cp`,
          {command}`ln`, {command}`mv` and {command}`rm` so that they
          prompt before deleting or overwriting files. Set to `no` to
          disable this safer behavior.
        '';
      };
    };
  };

in
{
  meta.maintainers = [ lib.maintainers.nickhu ];
  options = {
    programs.zsh = {
      prezto = mkOption {
        type = preztoModule;
        default = { };
        description = "Options to configure prezto.";
      };
    };
  };
  config = lib.mkIf cfg.enable (
    lib.mkMerge [
      {
        home.packages = [ cfg.package ];

        home.file."${relToDotDir ".zprofile"}".text = ''
          # Generated by Nix
          source ${cfg.package}/share/zsh-prezto/runcoms/zprofile
        '';
        home.file."${relToDotDir ".zlogin"}".text = ''
          # Generated by Nix
          source ${cfg.package}/share/zsh-prezto/runcoms/zlogin
        '';
        home.file."${relToDotDir ".zlogout"}".text = ''
          # Generated by Nix
          source ${cfg.package}/share/zsh-prezto/runcoms/zlogout
        '';
        # Using mkAfter to make sure we load Home-Manager's environment
        # variables first (see modules/prgrams/zsh.nix)
        home.file."${relToDotDir ".zshenv"}".text = lib.mkAfter ''
          # Generated by Nix
          source ${cfg.package}/share/zsh-prezto/runcoms/zshenv
        '';
        home.file."${relToDotDir ".zpreztorc"}".text = ''
          # Generated by Nix
          ${optionalString (cfg.caseSensitive != null) ''
            zstyle ':prezto:*:*' case-sensitive '${lib.hm.booleans.yesNo cfg.caseSensitive}'
          ''}
          ${optionalString (cfg.color != null) ''
            zstyle ':prezto:*:*' color '${lib.hm.booleans.yesNo cfg.color}'
          ''}
          ${optionalString (cfg.pmoduleDirs != [ ]) ''
            zstyle ':prezto:load' pmodule-dirs ${builtins.concatStringsSep " " cfg.pmoduleDirs}
          ''}
          ${optionalString (cfg.extraModules != [ ]) ''
            zstyle ':prezto:load' zmodule ${
              strings.concatMapStringsSep " " strings.escapeShellArg cfg.extraModules
            }
          ''}
          ${optionalString (cfg.extraFunctions != [ ]) ''
            zstyle ':prezto:load' zfunction ${
              strings.concatMapStringsSep " " strings.escapeShellArg cfg.extraFunctions
            }
          ''}
          ${optionalString (cfg.pmodules != [ ]) ''
            zstyle ':prezto:load' pmodule \
              ${strings.concatMapStringsSep " \\\n  " strings.escapeShellArg cfg.pmodules}
          ''}
          ${optionalString (cfg.autosuggestions.color != null) ''
            zstyle ':prezto:module:autosuggestions:color' found '${cfg.autosuggestions.color}'
          ''}
          ${optionalString (cfg.completions.ignoredHosts != [ ]) ''
            zstyle ':prezto:module:completion:*:hosts' etc-host-ignores \
              ${strings.concatMapStringsSep " " strings.escapeShellArg cfg.completions.ignoredHosts}
          ''}
          ${optionalString (cfg.editor.keymap != null) ''
            zstyle ':prezto:module:editor' key-bindings '${cfg.editor.keymap}'
          ''}
          ${optionalString (cfg.editor.dotExpansion != null) ''
            zstyle ':prezto:module:editor' dot-expansion '${lib.hm.booleans.yesNo cfg.editor.dotExpansion}'
          ''}
          ${optionalString (cfg.editor.promptContext != null) ''
            zstyle ':prezto:module:editor' ps-context '${lib.hm.booleans.yesNo cfg.editor.promptContext}'
          ''}
          ${optionalString (cfg.git.submoduleIgnore != null) ''
            zstyle ':prezto:module:git:status:ignore' submodules '${cfg.git.submoduleIgnore}'
          ''}
          ${optionalString (cfg.gnuUtility.prefix != null) ''
            zstyle ':prezto:module:gnu-utility' prefix '${cfg.gnuUtility.prefix}'
          ''}
          ${optionalString (cfg.historySubstring.foundColor != null) ''
            zstyle ':prezto:module:history-substring-search:color' found '${cfg.historySubstring.foundColor}'
          ''}
          ${optionalString (cfg.historySubstring.notFoundColor != null) ''
            zstyle ':prezto:module:history-substring-search:color' not-found '${cfg.historySubstring.notFoundColor}'
          ''}
          ${optionalString (cfg.historySubstring.globbingFlags != null) ''
            zstyle ':prezto:module:history-substring-search:color' globbing-flags '${cfg.historySubstring.globbingFlags}'
          ''}
          ${optionalString (cfg.macOS.dashKeyword != null) ''
            zstyle ':prezto:module:osx:man' dash-keyword '${cfg.macOS.dashKeyword}'
          ''}
          ${optionalString (cfg.prompt.theme != null) ''
            zstyle ':prezto:module:prompt' theme '${cfg.prompt.theme}'
          ''}
          ${optionalString (cfg.prompt.pwdLength != null) ''
            zstyle ':prezto:module:prompt' pwd-length '${cfg.prompt.pwdLength}'
          ''}
          ${optionalString (cfg.prompt.showReturnVal != null) ''
            zstyle ':prezto:module:prompt' show-return-val '${lib.hm.booleans.yesNo cfg.prompt.showReturnVal}'
          ''}
          ${optionalString (cfg.python.virtualenvAutoSwitch != null) ''
            zstyle ':prezto:module:python:virtualenv' auto-switch '${lib.hm.booleans.yesNo cfg.python.virtualenvAutoSwitch}'
          ''}
          ${optionalString (cfg.python.virtualenvInitialize != null) ''
            zstyle ':prezto:module:python:virtualenv' initialize '${lib.hm.booleans.yesNo cfg.python.virtualenvInitialize}'
          ''}
          ${optionalString (cfg.ruby.chrubyAutoSwitch != null) ''
            zstyle ':prezto:module:ruby:chruby' auto-switch '${lib.hm.booleans.yesNo cfg.ruby.chrubyAutoSwitch}'
          ''}
          ${optionalString (cfg.screen.autoStartLocal != null) ''
            zstyle ':prezto:module:screen:auto-start' local '${lib.hm.booleans.yesNo cfg.screen.autoStartLocal}'
          ''}
          ${optionalString (cfg.screen.autoStartRemote != null) ''
            zstyle ':prezto:module:screen:auto-start' remote '${lib.hm.booleans.yesNo cfg.screen.autoStartRemote}'
          ''}
          ${optionalString (cfg.ssh.identities != [ ]) ''
            zstyle ':prezto:module:ssh:load' identities \
              ${strings.concatMapStringsSep " " strings.escapeShellArg cfg.ssh.identities}
          ''}
          ${optionalString (cfg.syntaxHighlighting.highlighters != [ ]) ''
            zstyle ':prezto:module:syntax-highlighting' highlighters \
              ${strings.concatMapStringsSep " \\\n  " strings.escapeShellArg
                cfg.syntaxHighlighting.highlighters
              }
          ''}
          ${optionalString (cfg.syntaxHighlighting.styles != { }) ''
            zstyle ':prezto:module:syntax-highlighting' styles \
              ${builtins.concatStringsSep " \\\n" (
                lib.attrsets.mapAttrsToList (
                  k: v: strings.escapeShellArg k + " " + strings.escapeShellArg v
                ) cfg.syntaxHighlighting.styles
              )}
          ''}
          ${optionalString (cfg.syntaxHighlighting.pattern != { }) ''
            zstyle ':prezto:module:syntax-highlighting' pattern \
              ${builtins.concatStringsSep " \\\n" (
                lib.attrsets.mapAttrsToList (
                  k: v: strings.escapeShellArg k + " " + strings.escapeShellArg v
                ) cfg.syntaxHighlighting.pattern
              )}
          ''}
          ${optionalString (cfg.terminal.autoTitle != null) ''
            zstyle ':prezto:module:terminal' auto-title '${lib.hm.booleans.yesNo cfg.terminal.autoTitle}'
          ''}
          ${optionalString (cfg.terminal.windowTitleFormat != null) ''
            zstyle ':prezto:module:terminal:window-title' format '${cfg.terminal.windowTitleFormat}'
          ''}
          ${optionalString (cfg.terminal.tabTitleFormat != null) ''
            zstyle ':prezto:module:terminal:tab-title' format '${cfg.terminal.tabTitleFormat}'
          ''}
          ${optionalString (cfg.terminal.multiplexerTitleFormat != null) ''
            zstyle ':prezto:module:terminal:multiplexer-title' format '${cfg.terminal.multiplexerTitleFormat}'
          ''}
          ${optionalString (cfg.tmux.autoStartLocal != null) ''
            zstyle ':prezto:module:tmux:auto-start' local '${lib.hm.booleans.yesNo cfg.tmux.autoStartLocal}'
          ''}
          ${optionalString (cfg.tmux.autoStartRemote != null) ''
            zstyle ':prezto:module:tmux:auto-start' remote '${lib.hm.booleans.yesNo cfg.tmux.autoStartRemote}'
          ''}
          ${optionalString (cfg.tmux.itermIntegration != null) ''
            zstyle ':prezto:module:tmux:iterm' integrate '${lib.hm.booleans.yesNo cfg.tmux.itermIntegration}'
          ''}
          ${optionalString (cfg.tmux.defaultSessionName != null) ''
            zstyle ':prezto:module:tmux:session' name '${cfg.tmux.defaultSessionName}'
          ''}
          ${optionalString (cfg.utility.safeOps != null) ''
            zstyle ':prezto:module:utility' safe-ops '${lib.hm.booleans.yesNo cfg.utility.safeOps}'
          ''}
          ${cfg.extraConfig}
        '';

        programs.zsh.initContent = lib.mkOrder 850 ''
          # Load prezto
          source ${cfg.package}/share/zsh-prezto/runcoms/zshrc
        '';
      }
    ]
  );
}
