{ config, lib, pkgs, ... }:

with lib;

let

  cfg = config.programs.tmux;

  pluginName = p: if types.package.check p then p.pname else p.plugin.pname;

  pluginModule = types.submodule {
    options = {
      plugin = mkOption {
        type = types.package;
        description = "Path of the configuration file to include.";
      };

      extraConfig = mkOption {
        type = types.lines;
        description = "Additional configuration for the associated plugin.";
        default = "";
      };
    };
  };

  defaultKeyMode = "emacs";
  defaultResize = 5;
  defaultShortcut = "b";
  defaultTerminal = "screen";
  defaultShell = null;

  boolToStr = value: if value then "on" else "off";

  tmuxConf = ''
    ${optionalString cfg.sensibleOnTop ''
      # ============================================= #
      # Start with defaults from the Sensible plugin  #
      # --------------------------------------------- #
      run-shell ${pkgs.tmuxPlugins.sensible.rtp}
      # ============================================= #
    ''}
    set  -g default-terminal "${cfg.terminal}"
    set  -g base-index      ${toString cfg.baseIndex}
    setw -g pane-base-index ${toString cfg.baseIndex}
    ${optionalString (cfg.shell != null) ''
      # We need to set default-shell before calling new-session
      set  -g default-shell "${cfg.shell}"
    ''}
    ${optionalString cfg.newSession "new-session"}

    ${optionalString cfg.reverseSplit ''
      bind -N "Split the pane into two, left and right" v split-window -h
      bind -N "Split the pane into two, top and bottom" s split-window -v
    ''}

    set -g status-keys ${cfg.keyMode}
    set -g mode-keys   ${cfg.keyMode}

    ${optionalString
    (cfg.keyMode == "vi" && cfg.customPaneNavigationAndResize) ''
      bind -N "Select pane to the left of the active pane" h select-pane -L
      bind -N "Select pane below the active pane" j select-pane -D
      bind -N "Select pane above the active pane" k select-pane -U
      bind -N "Select pane to the right of the active pane" l select-pane -R

      bind -r -N "Resize the pane left by ${toString cfg.resizeAmount}" \
        H resize-pane -L ${toString cfg.resizeAmount}
      bind -r -N "Resize the pane down by ${toString cfg.resizeAmount}" \
        J resize-pane -D ${toString cfg.resizeAmount}
      bind -r -N "Resize the pane up by ${toString cfg.resizeAmount}" \
        K resize-pane -U ${toString cfg.resizeAmount}
      bind -r -N "Resize the pane right by ${toString cfg.resizeAmount}" \
        L resize-pane -R ${toString cfg.resizeAmount}
    ''}

    ${if cfg.prefix != null then ''
      # rebind main key: ${cfg.prefix}
      unbind C-${defaultShortcut}
      set -g prefix ${cfg.prefix}
      bind -N "Send the prefix key through to the application" \
        ${cfg.prefix} send-prefix
    '' else
      optionalString (cfg.shortcut != defaultShortcut) ''
        # rebind main key: C-${cfg.shortcut}
        unbind C-${defaultShortcut}
        set -g prefix C-${cfg.shortcut}
        bind -N "Send the prefix key through to the application" \
          ${cfg.shortcut} send-prefix
        bind C-${cfg.shortcut} last-window
      ''}

    ${optionalString cfg.disableConfirmationPrompt ''
      bind-key -N "Kill the current window" & kill-window
      bind-key -N "Kill the current pane" x kill-pane
    ''}

    set  -g mouse             ${boolToStr cfg.mouse}
    setw -g aggressive-resize ${boolToStr cfg.aggressiveResize}
    setw -g clock-mode-style  ${if cfg.clock24 then "24" else "12"}
    set  -s escape-time       ${toString cfg.escapeTime}
    set  -g history-limit     ${toString cfg.historyLimit}
  '';

  configPlugins = {
    assertions = [
      (let
        hasBadPluginName = p: !(hasPrefix "tmuxplugin" (pluginName p));
        badPlugins = filter hasBadPluginName cfg.plugins;
      in {
        assertion = badPlugins == [ ];
        message = ''Invalid tmux plugin (not prefixed with "tmuxplugins"): ''
          + concatMapStringsSep ", " pluginName badPlugins;
      })
    ];

    xdg.configFile."tmux/tmux.conf".text = ''
      # ============================================= #
      # Load plugins with Home Manager                #
      # --------------------------------------------- #

      ${(concatMapStringsSep "\n\n" (p: ''
        # ${pluginName p}
        # ---------------------
        ${p.extraConfig or ""}
        run-shell ${if types.package.check p then p.rtp else p.plugin.rtp}
      '') cfg.plugins)}
      # ============================================= #
    '';
  };

in {
  options = {
    programs.tmux = {
      aggressiveResize = mkOption {
        default = false;
        type = types.bool;
        description = ''
          Resize the window to the size of the smallest session for
          which it is the current window.
        '';
      };

      baseIndex = mkOption {
        default = 0;
        example = 1;
        type = types.ints.unsigned;
        description = "Base index for windows and panes.";
      };

      clock24 = mkOption {
        default = false;
        type = types.bool;
        description = "Use 24 hour clock.";
      };

      customPaneNavigationAndResize = mkOption {
        default = false;
        type = types.bool;
        description = ''
          Override the hjkl and HJKL bindings for pane navigation and
          resizing in VI mode.
        '';
      };

      disableConfirmationPrompt = mkOption {
        default = false;
        type = types.bool;
        description = ''
          Disable confirmation prompt before killing a pane or window
        '';
      };

      enable = mkEnableOption "tmux";

      escapeTime = mkOption {
        default = 500;
        example = 0;
        type = types.ints.unsigned;
        description = ''
          Time in milliseconds for which tmux waits after an escape is
          input.
        '';
      };

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

      historyLimit = mkOption {
        default = 2000;
        example = 5000;
        type = types.ints.positive;
        description = "Maximum number of lines held in window history.";
      };

      keyMode = mkOption {
        default = defaultKeyMode;
        example = "vi";
        type = types.enum [ "emacs" "vi" ];
        description = "VI or Emacs style shortcuts.";
      };

      mouse = mkEnableOption "mouse support";

      newSession = mkOption {
        default = false;
        type = types.bool;
        description = ''
          Automatically spawn a session if trying to attach and none
          are running.
        '';
      };

      package = mkOption {
        type = types.package;
        default = pkgs.tmux;
        defaultText = literalExpression "pkgs.tmux";
        example = literalExpression "pkgs.tmux";
        description = "The tmux package to install";
      };

      reverseSplit = mkOption {
        default = false;
        type = types.bool;
        description = "Reverse the window split shortcuts.";
      };

      resizeAmount = mkOption {
        default = defaultResize;
        example = 10;
        type = types.ints.positive;
        description = "Number of lines/columns when resizing.";
      };

      sensibleOnTop = mkOption {
        type = types.bool;
        default = true;
        description = ''
          Run the sensible plugin at the top of the configuration. It
          is possible to override the sensible settings using the
          {option}`programs.tmux.extraConfig` option.
        '';
      };

      prefix = mkOption {
        default = null;
        example = "C-a";
        type = types.nullOr types.str;
        description = ''
          Set the prefix key. Overrules the "shortcut" option when set.
        '';
      };

      shortcut = mkOption {
        default = defaultShortcut;
        example = "a";
        type = types.str;
        description = ''
          CTRL following by this key is used as the main shortcut.
        '';
      };

      terminal = mkOption {
        default = defaultTerminal;
        example = "screen-256color";
        type = types.str;
        description = "Set the $TERM variable.";
      };

      shell = mkOption {
        default = defaultShell;
        example = "\${pkgs.zsh}/bin/zsh";
        type = with types; nullOr str;
        description = "Set the default-shell tmux variable.";
      };

      secureSocket = mkOption {
        default = pkgs.stdenv.isLinux;
        type = types.bool;
        description = ''
          Store tmux socket under {file}`/run`, which is more
          secure than {file}`/tmp`, but as a downside it doesn't
          survive user logout.
        '';
      };

      tmuxp.enable = mkEnableOption "tmuxp";

      tmuxinator.enable = mkEnableOption "tmuxinator";

      plugins = mkOption {
        type = with types;
          listOf (either package pluginModule) // {
            description = "list of plugin packages or submodules";
          };
        description = ''
          List of tmux plugins to be included at the end of your tmux
          configuration. The sensible plugin, however, is defaulted to
          run at the top of your configuration.
        '';
        default = [ ];
        example = literalExpression ''
          with pkgs; [
            tmuxPlugins.cpu
            {
              plugin = tmuxPlugins.resurrect;
              extraConfig = "set -g @resurrect-strategy-nvim 'session'";
            }
            {
              plugin = tmuxPlugins.continuum;
              extraConfig = '''
                set -g @continuum-restore 'on'
                set -g @continuum-save-interval '60' # minutes
              ''';
            }
          ]
        '';
      };
    };
  };

  config = mkIf cfg.enable (mkMerge ([
    {
      home.packages = [ cfg.package ]
        ++ optional cfg.tmuxinator.enable pkgs.tmuxinator
        ++ optional cfg.tmuxp.enable pkgs.tmuxp;
    }

    { xdg.configFile."tmux/tmux.conf".text = mkBefore tmuxConf; }
    { xdg.configFile."tmux/tmux.conf".text = mkAfter cfg.extraConfig; }

    (mkIf cfg.secureSocket {
      home.sessionVariables = {
        TMUX_TMPDIR = ''''${XDG_RUNTIME_DIR:-"/run/user/$(id -u)"}'';
      };
    })

    (mkIf (cfg.plugins != [ ]) configPlugins)
  ]));
}
