# Interactive shell: zsh + tmux. Wanted on every host.
{
  config,
  lib,
  pkgs,
  inputs,
  ...
}:
let
  # Shared Catppuccin Mocha palette: raw 6-hex strings, no leading "#".
  ctp = import ../catppuccin-mocha.nix;
in
{
  imports = [
    # Prebuilt nix-index database -> working command-not-found
    # ("cmd not found -> which nix package provides it"), no manual indexing.
    inputs.nix-index-database.homeModules.default
  ];

  # CLI staples wanted on every host (search, parse, monitor). ripgrep/fd also
  # back fzf and editor integrations; tea is the Gitea CLI for code.emmathe.dev.
  home.packages = [
    pkgs.ripgrep
    pkgs.fd
    pkgs.jq
    pkgs.tea
    pkgs.hyperfine # command-line benchmarking
    pkgs.sd # saner find-and-replace than sed
  ];

  # Resource monitor, themed Catppuccin Mocha to match the rest of the desktop.
  # btop does not bundle the theme, so vendor it from catppuccin/btop (pinned).
  programs.btop = {
    enable = true;
    settings.color_theme = "catppuccin_mocha";
  };
  xdg.configFile."btop/themes/catppuccin_mocha.theme".source = pkgs.fetchurl {
    url = "https://raw.githubusercontent.com/catppuccin/btop/f437574b600f1c6d932627050b15ff5153b58fa3/themes/catppuccin_mocha.theme";
    hash = "sha256-THRpq5vaKCwf9gaso3ycC4TNDLZtBB5Ofh/tOXkfRkQ=";
  };

  programs.zsh = {
    enable = true;
    # Keep zsh dotfiles under XDG (~/.config/zsh) rather than the legacy $HOME
    # layout, matching xdg.enable. history.path is pinned below so the existing
    # ~/.zsh_history is reused, not orphaned by the dotDir move.
    dotDir = "${config.xdg.configHome}/zsh";
    enableCompletion = true;
    enableVteIntegration = true;
    autosuggestion.enable = true;
    # Bind Up/Down for history-substring-search in BOTH cursor-key modes: CSI
    # (^[[A/^[[B -- normal mode, and what the Linux TTY sends) and SS3
    # (^[OA/^[OB -- application mode, used by foot, tmux and iTerm2). Binding
    # only the default CSI form leaves it dead at the prompt in foot/iTerm2.
    historySubstringSearch = {
      enable = true;
      searchUpKey = [
        "^[[A"
        "^[OA"
      ];
      searchDownKey = [
        "^[[B"
        "^[OB"
      ];
    };
    history = {
      # Stay at the legacy ~/.zsh_history (default would follow dotDir into
      # ~/.config/zsh and orphan the existing file). Keeps history intact.
      path = "${config.home.homeDirectory}/.zsh_history";
      append = true; # append, don't overwrite, on shell exit
      size = 100000; # in-memory (HISTSIZE)
      save = 100000; # on-disk (SAVEHIST)
      ignoreDups = true; # drop consecutive duplicates
      ignoreSpace = true; # leading-space commands stay out of history
      expireDuplicatesFirst = true;
      share = true; # live-share history across sessions
      extended = true; # record timestamps
    };
    oh-my-zsh = {
      enable = true;
      plugins = [
        "git"
        "man"
        "sudo" # double-Esc prefixes the last command with sudo
        "colored-man-pages"
        "extract" # `extract <archive>` for any format
      ];
      theme = "robbyrussell";
    };
    syntaxHighlighting.enable = true;
    initContent = lib.mkMerge [
      # Auto-start tmux in every interactive terminal -- foot, iTerm2, the WSL
      # shell, the Linux console -- so a new terminal lands straight in the
      # multiplexer (session "main": attach if present, else create). Panes run
      # a plain non-login zsh (tmux's default-command "${SHELL}"). Order 200
      # runs before oh-my-zsh/compinit so the exec replaces the shell before
      # that setup is wasted. Guards, each preventing a real breakage:
      #   interactive only -> don't hijack scp / `ssh host cmd` / scripted shells
      #   $TMUX empty      -> a pane's zsh won't re-exec tmux (infinite loop)
      #   not SSH          -> don't force inbound SSH logins into a server tmux
      #   not VS Code      -> its integrated terminal manages itself
      #   tmux on PATH     -> a failed exec would otherwise kill the login shell
      #   $NO_TMUX unset   -> escape hatch: `NO_TMUX=1 <term>` opens a bare shell
      (lib.mkOrder 200 ''
        if [[ $- == *i* ]] \
          && [[ -z "$TMUX" ]] \
          && [[ -z "$NO_TMUX" ]] \
          && [[ -z "$SSH_CONNECTION" && -z "$SSH_TTY" ]] \
          && [[ "$TERM_PROGRAM" != "vscode" ]] \
          && command -v tmux >/dev/null 2>&1; then
          exec tmux new-session -A -s main
        fi
      '')
      # Prefix the prompt with the hostname over SSH (mkAfter).
      (lib.mkOrder 1500 ''
        if [ "$SSH_CLIENT" ] || [ "$SSH_TTY" ]; then
          export PS1="%M $PS1"
        fi
      '')
    ];
    shellAliases = {
      # eza's zsh integration also defines these; set explicitly so the
      # icons/git intent is obvious.
      ls = "eza --icons --git";
      ll = "eza --icons --git -l";
      la = "eza --icons --git -la";
      lt = "eza --icons --git --tree";
      cls = "clear";
    };
  };

  # Fuzzy finder: Ctrl-R fuzzy history, Ctrl-T files, Alt-C cd.
  programs.fzf = {
    enable = true;
    enableZshIntegration = true;
    # Catppuccin Mocha colours (rendered into FZF_DEFAULT_OPTS --color). Each
    # value needs a leading "#"; the palette stores raw hex.
    colors = {
      "bg" = "#${ctp.base}";
      "bg+" = "#${ctp.surface1}"; # current line / selected row
      "fg" = "#${ctp.text}";
      "fg+" = "#${ctp.text}";
      "hl" = "#${ctp.blue}"; # match highlights
      "hl+" = "#${ctp.blue}";
      "header" = "#${ctp.red}";
      "info" = "#${ctp.mauve}";
      "marker" = "#${ctp.green}";
      "pointer" = "#${ctp.pink}";
      "prompt" = "#${ctp.mauve}";
      "spinner" = "#${ctp.pink}";
      "border" = "#${ctp.surface1}";
    };
  };

  # Frecency directory jumping: `z <fragment>`.
  programs.zoxide = {
    enable = true;
    enableZshIntegration = true;
  };

  # Per-project environments auto-loaded on cd, with the Nix dev-shell cache.
  programs.direnv = {
    enable = true;
    nix-direnv.enable = true;
  };

  # Modern ls (drives the ls aliases above).
  programs.eza = {
    enable = true;
    git = true;
    icons = "auto"; # boolean form is deprecated
  };

  # Syntax-highlighting pager, used as `bat` (acts like cat when piped). bat
  # ships no Catppuccin theme, so vendor the upstream tmTheme from catppuccin/bat
  # (delta in git.nix reuses it as its syntax-theme).
  programs.bat = {
    enable = true;
    config.theme = "Catppuccin Mocha";
    themes."Catppuccin Mocha" = {
      src = pkgs.fetchFromGitHub {
        owner = "catppuccin";
        repo = "bat";
        rev = "6810349b28055dce54076712fc05fc68da4b8ec0";
        sha256 = "1y5sfi7jfr97z1g6vm2mzbsw59j1jizwlmbadvmx842m0i5ak5ll";
      };
      file = "themes/Catppuccin Mocha.tmTheme";
    };
  };

  # command-not-found backed by the prebuilt nix-index DB (module imported
  # above). `comma` runs an uninstalled program once: `, cowsay hi`.
  programs.nix-index.enable = true;
  programs.nix-index-database.comma.enable = true;

  # Nicer nixos-rebuild/home-manager (diffs) + $NH_FLAKE. No automatic clean:
  # the scheduled GC's only benefit is reclaiming disk, but it can reap store
  # paths the current generation still references (notably on nix-darwin, where
  # it broke completion by removing an in-use oh-my-zsh). GC manually instead:
  # `nh clean all` / `nix-collect-garbage -d` when nothing important is running.
  programs.nh = {
    enable = true;
    flake = "$HOME/code/nixfiles";
  };

  # GitHub CLI. `programs.gh.settings` is deliberately unset: home-manager renders
  # ~/.config/gh/config.yml as a read-only /nix/store symlink whenever the module
  # is enabled, but gh must rewrite that file on `gh auth login` and `gh config
  # set`, which then fail with a permission error. Suppress the managed config.yml
  # (below) and let gh own it. The token lives in hosts.yml, which is never
  # Nix-managed. Set the SSH protocol once at runtime: `gh config set git_protocol
  # ssh` (it can't be declarative here without recreating the immutable file).
  programs.gh.enable = true;
  xdg.configFile."gh/config.yml".enable = lib.mkForce false;

  programs.tmux = {
    enable = true;
    reverseSplit = true;
    # tmux-256color (not tmux-direct): the standard inside-tmux terminfo.
    # tmux-direct's capabilities desync zsh's line redraw on some terminals
    # (e.g. iTerm2 -> duplicated chars on Tab, stray newlines). Truecolor is
    # advertised per outer terminal via the RGB terminal-features below.
    terminal = "tmux-256color";
    newSession = true;
    keyMode = "vi";
    historyLimit = 500000;
    mouse = true;
    escapeTime = 10; # was the 500ms default -> laggy ESC in vim
    focusEvents = true; # let vim see focus changes (autoread)
    baseIndex = 1; # sets both base-index and pane-base-index

    plugins = with pkgs.tmuxPlugins; [
      sensible
      vim-tmux-navigator # Ctrl-h/j/k/l across vim splits and tmux panes
      yank
      extrakto # prefix+Tab: fzf-grab paths/URLs/text from the pane into the prompt
      {
        # Catppuccin Mocha statusline (v2 API: flavour + window options must be
        # set before the plugin loads, which home-manager does for plugin
        # extraConfig; the status modules below go in the main extraConfig,
        # which HM appends after all plugins).
        plugin = catppuccin;
        extraConfig = ''
          set -g @catppuccin_flavor 'mocha'
          set -g @catppuccin_window_status_style 'rounded'
        '';
      }
      resurrect # save/restore sessions
      {
        plugin = continuum; # auto-save + restore on tmux start (after resurrect)
        extraConfig = ''
          set -g @continuum-restore 'on'
        '';
      }
    ];
    # `reverseSplit = true` already binds s -> vertical and v -> horizontal
    # split (the dotfiles' vim-style splits).
    extraConfig = ''
      # Run a non-login shell in new panes/windows.
      set -g default-command "''${SHELL}"

      # Drop the stock split keys in favour of the s/v binds above.
      unbind %
      unbind '"'

      # Alt+Arrow pane navigation
      bind -n M-Left select-pane -L
      bind -n M-Right select-pane -R
      bind -n M-Up select-pane -U
      bind -n M-Down select-pane -D

      # Truecolor for the outer terminals (foot reports xterm-ish too; iTerm2 is
      # xterm-256color). Without this, with tmux-256color as default-terminal,
      # 24-bit colour would be quantised to 256.
      set -as terminal-features ",xterm-256color:RGB"
      # Tell tmux which capabilities the foot terminal supports, so truecolor,
      # synchronised output, the system clipboard (OSC 52), window titles and
      # cursor styling all pass through.
      set -as terminal-features ",foot*:RGB"
      set -as terminal-features ",foot*:sync"
      set -as terminal-features ",foot*:clipboard"
      set -as terminal-features ",foot*:title"
      set -as terminal-features ",foot*:ccolour"
      set -as terminal-features ",foot*:cstyle"

      # No home-manager options for these.
      set -g renumber-windows on
      set -g set-clipboard on

      # Catppuccin v2 statusline. Must run after the plugin has loaded;
      # home-manager appends this extraConfig after the whole plugin list.
      set -g status-left-length 100
      set -g status-right-length 100
      set -g status-left ""
      set -g status-right "#{E:@catppuccin_status_application}"
      set -ag status-right "#{E:@catppuccin_status_session}"
    '';
  };

  # Add the key to the agent on first use, so the passphrase is typed once per
  # login session rather than per commit/push (commit signing uses this agent).
  # The work box keeps its own ssh config (see work.nix), so this only
  # manages ~/.ssh/config on the personal hosts.
  programs.ssh = {
    enable = true;
    # The module's built-in default "*" block is being deprecated; opt out and
    # carry the defaults we want ourselves under settings."*".
    enableDefaultConfig = false;
    settings = {
      # Global defaults (rendered last, as ssh_config wants). AddKeysToAgent
      # adds the key on first use so the passphrase is typed once per session.
      "*" = {
        AddKeysToAgent = "yes";
        ForwardAgent = false;
        Compression = false;
        ServerAliveInterval = 0;
        ServerAliveCountMax = 3;
        HashKnownHosts = false;
        UserKnownHostsFile = "~/.ssh/known_hosts";
        ControlMaster = "no";
        ControlPath = "~/.ssh/master-%r@%n:%p";
        ControlPersist = "no";
      }
      # macOS: also cache the passphrase in the login keychain. UseKeychain
      # exists only in Apple's ssh; nixpkgs' openssh (which may be the `ssh` on
      # PATH) rejects it as "Bad configuration option". IgnoreUnknown (emitted
      # first by the module) makes any non-Apple ssh skip it instead of erroring,
      # while Apple's ssh still honours it. Darwin-only.
      // lib.optionalAttrs pkgs.stdenv.hostPlatform.isDarwin {
        IgnoreUnknown = "UseKeychain";
        UseKeychain = "yes";
      };
      # Gitea remote (the flake's origin) -- required on every host. HostName
      # pins the IP so it resolves without DNS. Port 30009 is non-default; pin
      # the dedicated key (identitiesOnly avoids "too many authentication
      # failures" when the agent holds several keys).
      "code.emmathe.dev" = {
        HostName = "10.187.1.76";
        User = "git";
        Port = 30009;
        IdentityFile = "~/.ssh/code.emmathe.dev";
        IdentitiesOnly = true;
      };
    };
  };

  # Run a user ssh-agent on Linux (macOS provides one via launchd). EDaaS also
  # enables this in the work module; both being true merges cleanly.
  services.ssh-agent.enable = lib.mkIf pkgs.stdenv.hostPlatform.isLinux true;

  # Classic process viewer (complements btop). htop has no custom-theme support
  # -- only a handful of built-in color schemes -- so it can't be hex-themed like
  # btop/bat/fzf. color_scheme = 0 (Default) draws from the terminal's ANSI
  # palette, which is Catppuccin Mocha (foot/iTerm2), so it matches by deferring
  # to the terminal rather than vendoring a theme.
  programs.htop = {
    enable = true;
    settings = {
      color_scheme = 0; # Default -> uses the terminal's Catppuccin palette
      delay = 15; # refresh every 1.5s
      cpu_count_from_one = 1;
      show_cpu_frequency = 1;
      show_cpu_usage = 1; # per-core usage shown in the CPU bars
      highlight_base_name = 1; # highlight the program name within the path
      highlight_megabytes = 1;
      highlight_threads = 1;
      hide_kernel_threads = 1;
      show_program_path = 0; # show just the command, not the full path
      tree_view = 1; # start in process-tree mode
      tree_view_always_by_pid = 0;
      account_guest_in_cpu_meter = 0;
      fields = with config.lib.htop.fields; [
        PID
        USER
        PRIORITY
        NICE
        M_SIZE
        M_RESIDENT
        M_SHARE
        STATE
        PERCENT_CPU
        PERCENT_MEM
        TIME
        COMM
      ];
    }
    // (
      with config.lib.htop;
      leftMeters [
        (bar "AllCPUs2")
        (bar "Memory")
        (bar "Swap")
      ]
    )
    // (
      with config.lib.htop;
      rightMeters [
        (text "Tasks")
        (text "LoadAverage")
        (text "Uptime")
      ]
    );
  };

  # Drop the zsh completion dump on every activation. A stale .zcompdump caches
  # /nix/store paths to completion functions; once a rebuild or a manual GC
  # removes them, compinit fails with "_git: function definition file not found"
  # for every completion. Deleting it forces a fresh rebuild from the current
  # fpath on the next shell. compinit dumps to $ZDOTDIR (~/.config/zsh now); the
  # $HOME and cache paths are also swept to clear any legacy leftovers.
  home.activation.resetZcompdump = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
    $DRY_RUN_CMD rm -f \
      "${config.xdg.configHome}"/zsh/.zcompdump* \
      "$HOME"/.zcompdump* \
      "''${XDG_CACHE_HOME:-$HOME/.cache}"/zsh/.zcompdump* 2>/dev/null || true
  '';
}
