# Declarative Sway window manager, status bar, lock, idle and notifications.
# Imported via ./desktop.nix, so only graphical hosts get it.
#
# The compositor binary, PAM and the polkit *daemon* come from the system-level
# programs.sway (see ../swaywm.nix); package = null below reuses it instead of
# pulling a second Sway. The polkit authentication *agent* (the thing that draws
# the GUI auth dialog) is a user service started here. home-manager owns the user
# config (~/.config/sway) and wires the systemd user session (sway-session.target),
# which is what lets the agent/swayidle/dunst/kanshi user services start with the
# desktop.
{
  pkgs,
  lib,
  # Threaded from mkHost (flake.nix). Desktop hosts set this false to drop
  # mobile components (battery block, screen-brightness keys).
  portable ? true,
  ...
}:
let
  # Catppuccin Mocha (shared with the ReGreet greeter). Raw hex; prefix "#"
  # where a consumer needs it -- Sway/i3status/dunst want "#", foot/swaylock do
  # not.
  ctp = import ../catppuccin-mocha.nix;

  # Focused-window screenshot -> swappy editor (the dotfiles' grimshot.sh logic).
  # Full store paths so it needs nothing on PATH.
  screenshotWindow = pkgs.writeShellScript "screenshot-window" ''
    ${pkgs.grim}/bin/grim -g "$(${pkgs.sway}/bin/swaymsg -t get_tree \
      | ${pkgs.jq}/bin/jq -r '.. | select(.focused?) | .rect | "\(.x),\(.y) \(.width)x\(.height)"')" \
      - | ${pkgs.swappy}/bin/swappy -f -
  '';

  # Binding-mode names. The string is both the `modes` attr key and what the
  # bar's mode indicator shows, so the keys are spelled out in the label.
  layoutMode = "layout:  [s]tacking  [w]tabbed  [e]split";
  systemMode = "system:  [l]ock  [e]xit  [s]leep  [r]eboot  [Shift+s]shutdown";
in
{
  wayland.windowManager.sway = {
    enable = true;
    package = null;
    # `sway --validate` needs a real package; skip it since package = null.
    checkConfig = false;
    config = rec {
      modifier = "Mod4";
      terminal = "${pkgs.foot}/bin/foot";
      # Launcher: sway-launcher-desktop running inside a floating foot window.
      menu = "${pkgs.foot}/bin/foot --app-id=launcher ${pkgs.sway-launcher-desktop}/bin/sway-launcher-desktop";

      # Dvorak is a variant of the "us" layout, not a standalone layout --
      # `xkb_layout = "dvorak"` fails to compile (no symbols/dvorak) and wlroots
      # silently falls back to QWERTY. Use the variant.
      input."type:keyboard" = {
        xkb_layout = "us";
        xkb_variant = "dvorak";
      };
      # Touchpads (laptops): tap-to-click and natural scrolling. Inert on the
      # desktop hosts, which have no touchpad.
      input."type:touchpad" = {
        tap = "enabled";
        natural_scroll = "enabled";
      };

      # Solid Catppuccin Mocha base as the wallpaper (no image dependency).
      output."*".bg = "#${ctp.base} solid_color";

      # Window borders -- Catppuccin Mocha (blue accent on the focused window).
      colors = {
        focused = {
          border = "#${ctp.blue}";
          background = "#${ctp.base}";
          text = "#${ctp.text}";
          indicator = "#${ctp.blue}";
          childBorder = "#${ctp.blue}";
        };
        focusedInactive = {
          border = "#${ctp.surface0}";
          background = "#${ctp.base}";
          text = "#${ctp.subtext0}";
          indicator = "#${ctp.surface0}";
          childBorder = "#${ctp.surface0}";
        };
        unfocused = {
          border = "#${ctp.surface0}";
          background = "#${ctp.base}";
          text = "#${ctp.subtext0}";
          indicator = "#${ctp.surface0}";
          childBorder = "#${ctp.surface0}";
        };
        urgent = {
          border = "#${ctp.red}";
          background = "#${ctp.base}";
          text = "#${ctp.text}";
          indicator = "#${ctp.red}";
          childBorder = "#${ctp.red}";
        };
      };

      window.commands = [
        {
          criteria.app_id = "launcher";
          command = "floating enable, resize set 800 500";
        }
        # Don't let swayidle blank/lock during fullscreen video. Two rules cover
        # native Wayland (app_id) and XWayland (class) clients.
        {
          criteria.app_id = ".*";
          command = "inhibit_idle fullscreen";
        }
        {
          criteria.class = ".*";
          command = "inhibit_idle fullscreen";
        }
      ];

      # Binding modes (submenus). Entered from keybindings below; each action
      # returns to the default mode. mkOptionDefault-merged with the module's
      # built-in "resize" mode.
      modes = {
        # Layout submenu (Mod+y). Mirrors Sway's default s/w/e layout keys --
        # notably it restores split-toggle, which moved off Mod+e when that
        # became the nemo launcher.
        ${layoutMode} = {
          "s" = "layout stacking, mode default";
          "w" = "layout tabbed, mode default";
          "e" = "layout toggle split, mode default";
          "Return" = "mode default";
          "Escape" = "mode default";
        };
        # Power menu (Mod+Shift+x). Lock reuses the themed swaylock; the rest go
        # through systemd/logind (allowed for the active local session).
        ${systemMode} = {
          "l" = "exec ${pkgs.swaylock}/bin/swaylock -f, mode default";
          "e" = "exec ${pkgs.sway}/bin/swaymsg exit, mode default";
          "s" = "exec systemctl suspend, mode default";
          "r" = "exec systemctl reboot, mode default";
          "Shift+s" = "exec systemctl poweroff, mode default";
          "Return" = "mode default";
          "Escape" = "mode default";
        };
      };

      bars = [
        {
          position = "top";
          statusCommand = "${pkgs.i3status-rust}/bin/i3status-rs ~/.config/i3status-rust/config-default.toml";
          fonts = {
            names = [
              "Noto Sans"
              "Font Awesome 6 Free"
            ];
            size = 11.0;
          };
          # Bar background + workspace buttons -- Catppuccin Mocha. The mantle
          # background matches i3status-rust's idle_bg below for a uniform strip.
          colors = {
            background = "#${ctp.mantle}";
            statusline = "#${ctp.text}";
            separator = "#${ctp.surface0}";
            focusedWorkspace = {
              border = "#${ctp.blue}";
              background = "#${ctp.blue}";
              text = "#${ctp.base}";
            };
            activeWorkspace = {
              border = "#${ctp.surface0}";
              background = "#${ctp.surface0}";
              text = "#${ctp.text}";
            };
            inactiveWorkspace = {
              border = "#${ctp.mantle}";
              background = "#${ctp.mantle}";
              text = "#${ctp.subtext0}";
            };
            urgentWorkspace = {
              border = "#${ctp.red}";
              background = "#${ctp.red}";
              text = "#${ctp.base}";
            };
          };
        }
      ];

      # NB: this whole set is wrapped in mkOptionDefault so it MERGES with the
      # home-manager module's default keybindings (same priority) rather than
      # replacing them. Do not wrap it in mkMerge with a normal-priority attr --
      # that makes the normal-priority def win and silently drops every default
      # bind (terminal, movement, workspaces, ...).
      keybindings = lib.mkOptionDefault (
        {
          # Launcher on Mod+Space. mkForce overrides the module's own default
          # Mod+Space (focus mode_toggle); a plain value would conflict with it
          # at equal priority. Mod+d also still runs the launcher (module default).
          "${modifier}+space" = lib.mkForce "exec ${menu}";
          # File manager. mkForce overrides the module default (layout toggle split).
          "${modifier}+e" = lib.mkForce "exec ${pkgs.nemo}/bin/nemo";
          "${modifier}+l" = "exec ${pkgs.swaylock}/bin/swaylock -f";

          # Cycle workspaces.
          "${modifier}+z" = "workspace prev";
          "${modifier}+x" = "workspace next";

          # focus mode_toggle (tiling <-> floating focus) -- re-homed off
          # Mod+Space, which is now the launcher.
          "${modifier}+Mod1+space" = "focus mode_toggle";

          # Enter the binding-mode submenus defined above.
          "${modifier}+y" = "mode \"${layoutMode}\"";
          "${modifier}+Shift+x" = "mode \"${systemMode}\"";

          # Clipboard history: pick a past entry through fuzzel (clipman stores
          # it -- see services.clipman below).
          "${modifier}+c" =
            "exec ${pkgs.clipman}/bin/clipman pick -t CUSTOM --tool-args=\"${pkgs.fuzzel}/bin/fuzzel --dmenu\"";

          # Screenshots -> swappy editor: Print = drag a region, Shift+Print =
          # the focused window.
          "Print" =
            "exec ${pkgs.grim}/bin/grim -g \"$(${pkgs.slurp}/bin/slurp)\" - | ${pkgs.swappy}/bin/swappy -f -";
          "Shift+Print" = "exec ${screenshotWindow}";

          "XF86AudioRaiseVolume" = "exec ${pkgs.wireplumber}/bin/wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+";
          "XF86AudioLowerVolume" = "exec ${pkgs.wireplumber}/bin/wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-";
          "XF86AudioMute" = "exec ${pkgs.wireplumber}/bin/wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle";
          "XF86AudioMicMute" = "exec ${pkgs.wireplumber}/bin/wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle";

          # Media keys (MPRIS via playerctl).
          "XF86AudioPlay" = "exec ${pkgs.playerctl}/bin/playerctl play-pause";
          "XF86AudioNext" = "exec ${pkgs.playerctl}/bin/playerctl next";
          "XF86AudioPrev" = "exec ${pkgs.playerctl}/bin/playerctl previous";
        }
        # Screen backlight: laptops only (no internal backlight on a desktop).
        // lib.optionalAttrs portable {
          "XF86MonBrightnessUp" = "exec ${pkgs.brightnessctl}/bin/brightnessctl set 5%+";
          "XF86MonBrightnessDown" = "exec ${pkgs.brightnessctl}/bin/brightnessctl set 5%-";
        }
      );
    };
  };

  # Terminal: Catppuccin Mocha. foot reads ~/.config/foot/foot.ini; the Sway
  # `terminal` above still launches the same binary, now themed.
  programs.foot = {
    enable = true;
    # foot 1.27: the bare [colors] section is deprecated in favour of
    # [colors-dark] (the default theme), and the cursor colour moved out of
    # [cursor] (where `color` is now rejected) into a `cursor` key here, written
    # "<text> <cursor>" (man foot.ini(5): "ff0000 00ff00" => green cursor, red
    # text). Only colors-dark is needed; we never set initial-color-theme=light.
    settings = {
      main = {
        # Nerd Font: monospace plus the powerline/Nerd glyphs the tmux
        # statusline uses (otherwise they render as blank/"?").
        font = "JetBrainsMono Nerd Font:size=11";
        # Advertise as xterm-256color so remote hosts without foot's terminfo
        # still behave (tmux re-adds foot's RGB/sync/etc. features -- see
        # shell.nix). The [main] section is the unheadered top of foot.ini.
        term = "xterm-256color";
      };
      scrollback.lines = 100000;
      "colors-dark" = {
        background = ctp.base;
        foreground = ctp.text;
        regular0 = ctp.surface1;
        regular1 = ctp.red;
        regular2 = ctp.green;
        regular3 = ctp.yellow;
        regular4 = ctp.blue;
        regular5 = ctp.pink;
        regular6 = ctp.teal;
        regular7 = ctp.subtext1;
        bright0 = ctp.surface2;
        bright1 = ctp.red;
        bright2 = ctp.green;
        bright3 = ctp.yellow;
        bright4 = ctp.blue;
        bright5 = ctp.pink;
        bright6 = ctp.teal;
        bright7 = ctp.subtext0;
        "selection-foreground" = ctp.base;
        "selection-background" = ctp.rosewater;
        cursor = "${ctp.base} ${ctp.rosewater}";
      };
    };
  };

  # Clipboard history: a user service runs `wl-paste --watch clipman store`,
  # bound to the Wayland session, so copies persist and Mod+c (above) can pick
  # an old entry through fuzzel.
  services.clipman.enable = true;

  # Polkit authentication agent. programs.sway (system) enables the polkit
  # daemon but no agent, so GUI privilege prompts (nemo mounting a disk,
  # NetworkManager/blueman editing a system resource) would otherwise fail
  # silently. lxqt-policykit is a small, toolkit-light agent; bind it to the
  # Sway session so it starts and stops with the desktop.
  systemd.user.services.polkit-lxqt = {
    Unit = {
      Description = "lxqt-policykit polkit authentication agent";
      PartOf = [ "graphical-session.target" ];
      After = [ "graphical-session.target" ];
    };
    Service = {
      ExecStart = "${pkgs.lxqt.lxqt-policykit}/bin/lxqt-policykit-agent";
      Restart = "on-failure";
    };
    Install.WantedBy = [ "sway-session.target" ];
  };

  # Output/display management. Reacts to hotplug and applies per-display
  # mode/scale/position. Profiles are hardware-specific: the safe default below
  # just enables the internal laptop panel; add docked/desktop profiles with the
  # real identifiers from `swaymsg -t get_outputs` (e.g. the Mac Pro's Apple
  # Cinema Display with its scale, or a docked laptop + external monitor).
  services.kanshi = {
    enable = true;
    settings = [
      {
        profile.name = "undocked";
        profile.outputs = [
          {
            criteria = "eDP-1";
            status = "enable";
          }
        ];
      }
      # Example to copy per host (fill in real criteria/mode/scale/position):
      # {
      #   profile.name = "desktop";
      #   profile.outputs = [
      #     { criteria = "Apple Computer Inc Cinema HD ..."; mode = "2560x1600"; scale = 1.0; position = "0,0"; status = "enable"; }
      #   ];
      # }
    ];
  };

  # Night light. Manual location (no geoclue dependency); adjust the coordinates
  # to taste. Warmer at night, neutral by day.
  services.gammastep = {
    enable = true;
    provider = "manual";
    latitude = 51.5;
    longitude = -0.13; # London-ish; set to your actual location
    temperature = {
      day = 6500;
      night = 3700;
    };
  };

  # fuzzel: the dmenu picker used by clipman, themed Catppuccin Mocha to match
  # (fuzzel colours are RRGGBBAA -- 8 hex digits).
  programs.fuzzel = {
    enable = true;
    settings = {
      main = {
        font = "Noto Sans:size=12";
        prompt = "\"clipboard \"";
      };
      border = {
        width = 2;
        radius = 8;
      };
      colors = {
        background = "${ctp.base}f0";
        text = "${ctp.text}ff";
        prompt = "${ctp.subtext0}ff";
        input = "${ctp.text}ff";
        match = "${ctp.blue}ff";
        selection = "${ctp.surface1}ff";
        selection-text = "${ctp.text}ff";
        selection-match = "${ctp.blue}ff";
        border = "${ctp.blue}ff";
      };
    };
  };

  programs.swaylock = {
    enable = true;
    # Catppuccin Mocha (swaylock colours are hex without "#").
    settings = {
      color = ctp.base;
      indicator-radius = 100;
      indicator-thickness = 7;
      show-failed-attempts = true;
      font = "Noto Sans";

      inside-color = ctp.base;
      inside-clear-color = ctp.base;
      inside-ver-color = ctp.base;
      inside-wrong-color = ctp.base;

      ring-color = ctp.surface1;
      ring-clear-color = ctp.yellow;
      ring-ver-color = ctp.blue;
      ring-wrong-color = ctp.red;

      key-hl-color = ctp.blue;
      bs-hl-color = ctp.red;

      line-color = ctp.base;
      line-clear-color = ctp.base;
      line-ver-color = ctp.base;
      line-wrong-color = ctp.base;
      separator-color = "00000000";

      text-color = ctp.text;
      text-clear-color = ctp.text;
      text-ver-color = ctp.text;
      text-wrong-color = ctp.text;
    };
  };

  # Lock on idle, turn screens off shortly after, and lock before sleep.
  services.swayidle = {
    enable = true;
    timeouts = [
      {
        timeout = 300;
        command = "${pkgs.swaylock}/bin/swaylock -f";
      }
      {
        timeout = 600;
        command = "${pkgs.sway}/bin/swaymsg 'output * power off'";
        resumeCommand = "${pkgs.sway}/bin/swaymsg 'output * power on'";
      }
    ];
    events = {
      before-sleep = "${pkgs.swaylock}/bin/swaylock -f";
      lock = "${pkgs.swaylock}/bin/swaylock -f";
    };
  };

  services.dunst = {
    enable = true;
    # Catppuccin Mocha notifications (dunst colours need a leading "#").
    settings = {
      global = {
        font = "Noto Sans 11";
        frame_color = "#${ctp.blue}";
        frame_width = 2;
        separator_color = "frame";
        offset = "10x10";
        corner_radius = 5;
      };
      urgency_low = {
        background = "#${ctp.base}";
        foreground = "#${ctp.text}";
        frame_color = "#${ctp.surface1}";
      };
      urgency_normal = {
        background = "#${ctp.base}";
        foreground = "#${ctp.text}";
        frame_color = "#${ctp.blue}";
      };
      urgency_critical = {
        background = "#${ctp.base}";
        foreground = "#${ctp.text}";
        frame_color = "#${ctp.peach}";
        timeout = 0;
      };
    };
  };

  programs.i3status-rust = {
    enable = true;
    bars.default = {
      # Catppuccin Mocha: a flat "plain" base recoloured via overrides. Idle
      # blocks sit on mantle (matching the Sway bar background) with light text;
      # only warning/critical states get a loud tinted background. The `theme`
      # bar option is shallow-merged away by `settings.theme`, so set the base
      # theme and its overrides together here.
      icons = "awesome6";
      settings.theme = {
        theme = "plain";
        overrides = {
          idle_bg = "#${ctp.mantle}";
          idle_fg = "#${ctp.text}";
          info_bg = "#${ctp.mantle}";
          info_fg = "#${ctp.blue}";
          good_bg = "#${ctp.mantle}";
          good_fg = "#${ctp.green}";
          warning_bg = "#${ctp.peach}";
          warning_fg = "#${ctp.base}";
          critical_bg = "#${ctp.red}";
          critical_fg = "#${ctp.base}";
          separator_bg = "#${ctp.mantle}";
          separator_fg = "#${ctp.surface1}";
        };
      };
      blocks = [
        {
          block = "disk_space";
          path = "/";
          format = " $icon $available ";
        }
        {
          block = "memory";
          format = " $icon $mem_used_percents ";
        }
        {
          block = "cpu";
          interval = 2;
        }
      ]
      # Desktop-only: CPU temperature and wired network throughput, in place
      # of the laptop's battery readout.
      ++ lib.optionals (!portable) [
        {
          block = "temperature";
          interval = 5;
          format = " $icon $average avg, $max max ";
        }
        { block = "net"; }
      ]
      ++ [ { block = "sound"; } ]
      ++ lib.optional portable { block = "battery"; }
      ++ [
        {
          block = "time";
          interval = 5;
          format = " $timestamp.datetime(f:'%a %d/%m %R') ";
        }
      ];
    };
  };
}
