# Opt-in services & integrations — off by default, one toggle each, enabled
# in system.nix (see the commented examples in the downstream template).
# These are system-side: their own `nomarchy.services.*` namespace, distinct
# from the distro's core `nomarchy.system.*` toggles. `username` (from
# specialArgs) is read lazily, only where a service needs it.
{ config, lib, pkgs, ... }@args:

let
  cfg = config.nomarchy.services;
in
{
  options.nomarchy.services = {
    tailscale.enable = lib.mkEnableOption ''
      Tailscale, the mesh VPN — ships the daemon and makes the login user its
      operator, so `tailscale up/down/set` and the System → VPN menu work
      without sudo. Authenticate once (the menu's Connect, or `tailscale up`)'';

    syncthing.enable = lib.mkEnableOption ''
      Syncthing continuous file sync, running as the login user — add
      folders and devices in the web UI at http://127.0.0.1:8384'';

    podman.enable = lib.mkEnableOption ''
      Podman for rootless containers, with `docker` aliased to it and DNS on
      the default network; the login user gets a subuid/subgid range'';

    flatpak.enable = lib.mkEnableOption ''
      Flatpak, with the Flathub remote added system-wide — install apps with
      `flatpak install flathub <app>`'';

    pika.enable = lib.mkEnableOption ''
      Pika Backup, a friendly GUI for scheduled Borg backups (the app;
      configure repositories and schedules inside it)'';

    steam.enable = lib.mkEnableOption ''
      Steam (Valve's game store/launcher) via programs.steam — which wires up
      the 32-bit graphics libraries, controller udev rules and Remote-Play
      firewall ports a bare package can't. Unfree'';

    libvirt.enable = lib.mkEnableOption ''
      libvirt/KVM virtualisation with the virt-manager GUI — runs the
      libvirtd daemon and adds the login user to the `libvirtd` group, so VMs
      can be managed without root'';

    obs.enable = lib.mkEnableOption ''
      OBS Studio for screen recording and streaming, plus a v4l2loopback
      virtual camera — so an OBS scene can be selected as a webcam in Zoom,
      Teams or a browser (screen capture itself already works over the
      desktop's PipeWire portal)'';

    docker.enable = lib.mkEnableOption ''
      Docker (rootful) with the login user in the `docker` group. Mutually
      exclusive with the podman toggle's docker-compat (both provide the
      `docker` command) — enable only one'';

    kdeconnect.enable = lib.mkEnableOption ''
      KDE Connect for phone integration — shared clipboard, notifications and
      file transfer with a paired phone; opens the firewall ports it needs'';

    gamemode.enable = lib.mkEnableOption ''
      Feral GameMode — a daemon games ask to apply a performance CPU governor
      and IO/nice priorities while running (via `gamemoderun`)'';

    adb.enable = lib.mkEnableOption ''
      the Android platform tools (adb/fastboot) for flashing/debugging phones.
      systemd ≥258 applies the device uaccess udev rules automatically, so the
      tools alone are enough — no group dance'';

    wireshark.enable = lib.mkEnableOption ''
      Wireshark (the Qt GUI) for packet capture, with the login user in the
      `wireshark` group so capture works without root'';

    ollama.enable = lib.mkEnableOption ''
      Ollama, a local LLM runtime serving an API on 127.0.0.1:11434 (pair it
      with the lmstudio/alpaca GUIs). CPU by default — set
      `services.ollama.acceleration` natively for GPU offload'';

    printing.enable = lib.mkEnableOption ''
      CUPS printing with Avahi/mDNS, so network printers are auto-discovered
      (add vendor drivers via `services.printing.drivers`); the menu's
      System ▸ Printers entry opens the system-config-printer GUI'' // { default = config.nomarchy.settings.printing.enable or false; };

    openrgb.enable = lib.mkEnableOption ''
      the OpenRGB daemon and GUI for controlling RGB lighting on peripherals
      and (where supported) motherboards — installs the udev rules for device
      access. Some motherboard controllers also need `hardware.i2c.enable`'';

    restic = {
      enable = lib.mkEnableOption ''
        a scheduled (daily) restic backup — headless, complementing the Pika
        GUI. Backs up `paths` with sane retention (7 daily / 4 weekly /
        6 monthly); needs a `repository` and a `passwordFile` (below). List
        and restore with the generated `restic-nomarchy` wrapper (repo +
        password baked in), e.g. `restic-nomarchy snapshots`'';

      repository = lib.mkOption {
        type = lib.types.str;
        default = "";
        example = "/mnt/backup/restic";
        description = ''
          The restic repository as a path or URL — e.g. `/mnt/backup/restic`,
          `sftp:user@host:/srv/restic`, `b2:bucket:/path`. Required when
          `restic.enable` is set.
        '';
      };

      passwordFile = lib.mkOption {
        type = lib.types.nullOr lib.types.str;
        default = null;
        example = "/etc/nomarchy/restic-password";
        description = ''
          Path to a file holding the repository password. Give an absolute
          path that lives ON THE MACHINE — not a `./file` in the flake, which
          would copy the secret into the world-readable Nix store. Required
          when `restic.enable` is set.
        '';
      };

      paths = lib.mkOption {
        type = lib.types.listOf lib.types.str;
        default = [ "/home" ];
        description = "Paths to include in the backup.";
      };
    };
  };

  config = lib.mkMerge [
    (lib.mkIf cfg.tailscale.enable {
      services.tailscale.enable = true;
      # Let the login user drive tailscale (up/down/set — and so the VPN menu's
      # Tailscale controls) without sudo. It's already in wheel, so the operator
      # grant is no real new privilege, just skips the password prompt. The
      # module's tailscaled-set unit applies this after the daemon starts.
      # mkDefault so a downstream can drop or replace it (e.g. extraSetFlags = []).
      services.tailscale.extraSetFlags = lib.mkDefault [ "--operator=${args.username}" ];
    })

    (lib.mkIf cfg.syncthing.enable {
      services.syncthing = {
        enable = true;
        user = args.username;
        # Manage folders/devices in the GUI; don't let the (empty) declarative
        # config wipe them on a rebuild.
        overrideDevices = false;
        overrideFolders = false;
        guiAddress = "127.0.0.1:8384";
      };
    })

    (lib.mkIf cfg.podman.enable {
      virtualisation.podman = {
        enable = true;
        dockerCompat = lib.mkDefault true; # `docker` → podman (off if you run real docker)
        defaultNetwork.settings.dns_enabled = true; # DNS for rootless containers
      };
      # Rootless podman needs a subuid/subgid range for the user.
      users.users.${args.username}.autoSubUidGidRange = lib.mkDefault true;
    })

    (lib.mkIf cfg.flatpak.enable {
      services.flatpak.enable = true;
      # Flatpak ships no remotes; add Flathub system-wide. Idempotent
      # (--if-not-exists), and it reads the .flatpakrepo over the network, so
      # it waits for connectivity and simply retries next boot if offline.
      systemd.services.nomarchy-flathub = {
        description = "Add the Flathub Flatpak remote";
        wantedBy = [ "multi-user.target" ];
        wants = [ "network-online.target" ];
        after = [ "network-online.target" ];
        path = [ pkgs.flatpak ];
        serviceConfig.Type = "oneshot";
        script = ''
          flatpak remote-add --system --if-not-exists flathub \
            https://dl.flathub.org/repo/flathub.flatpakrepo
        '';
      };
    })

    (lib.mkIf cfg.pika.enable {
      environment.systemPackages = [ pkgs.pika-backup ];
    })

    (lib.mkIf cfg.steam.enable {
      # The Steam package alone misses the system bits a game needs: the
      # 32-bit graphics stack, controller udev rules, Remote-Play ports.
      programs.steam.enable = true;
    })

    (lib.mkIf cfg.libvirt.enable {
      virtualisation.libvirtd.enable = true;
      programs.virt-manager.enable = true;
      # Group membership lets the user reach the system libvirtd socket
      # (manage VMs) without sudo. Merges with the user's other extraGroups.
      users.users.${args.username}.extraGroups = [ "libvirtd" ];
    })

    (lib.mkIf cfg.obs.enable {
      environment.systemPackages = [ pkgs.obs-studio ];
      # Virtual camera: OBS streams a scene into a v4l2 loopback device that
      # Zoom/Teams/browsers then pick up as a webcam. exclusive_caps=1 is the
      # bit that makes those apps actually list the device.
      boot.extraModulePackages = [ config.boot.kernelPackages.v4l2loopback ];
      boot.kernelModules = [ "v4l2loopback" ];
      boot.extraModprobeConfig = ''
        options v4l2loopback devices=1 video_nr=10 card_label="OBS Virtual Camera" exclusive_caps=1
      '';
    })

    (lib.mkIf cfg.docker.enable {
      virtualisation.docker.enable = true;
      users.users.${args.username}.extraGroups = [ "docker" ];
      # Podman's docker-compat and real Docker both claim the `docker`
      # command — guard against enabling both (unless you turn that off).
      assertions = [{
        assertion = !(cfg.podman.enable && config.virtualisation.podman.dockerCompat);
        message = ''
          nomarchy.services: docker and podman both provide the `docker`
          command. Enable only one, or set
          `virtualisation.podman.dockerCompat = false`.
        '';
      }];
    })

    (lib.mkIf cfg.kdeconnect.enable {
      programs.kdeconnect.enable = true; # installs the app and opens its ports
    })

    (lib.mkIf cfg.gamemode.enable {
      programs.gamemode.enable = true;
    })

    (lib.mkIf cfg.adb.enable {
      # `programs.adb` was removed — systemd ≥258 handles the device uaccess
      # udev rules, so the tools package is all that's needed now.
      environment.systemPackages = [ pkgs.android-tools ];
    })

    (lib.mkIf cfg.wireshark.enable {
      programs.wireshark = {
        enable = true;
        package = pkgs.wireshark; # the Qt GUI (the module defaults to the CLI)
      };
      users.users.${args.username}.extraGroups = [ "wireshark" ];
    })

    (lib.mkIf cfg.ollama.enable {
      services.ollama.enable = true;
    })

    (lib.mkIf cfg.printing.enable {
      services.printing.enable = true;
      # mDNS so network printers show up without manual setup.
      services.avahi = {
        enable = true;
        nssmdns4 = true;
        openFirewall = true;
      };
      # The CUPS admin GUI — the menu's System ▸ Printers entry execs it
      # (self-gated on this binary), so it ships with the printing service.
      environment.systemPackages = [ pkgs.system-config-printer ];
    })

    (lib.mkIf cfg.openrgb.enable {
      services.hardware.openrgb.enable = true;
    })

    (lib.mkIf cfg.restic.enable {
      assertions = [
        {
          assertion = cfg.restic.repository != "";
          message = "nomarchy.services.restic.repository must be set when restic is enabled.";
        }
        {
          assertion = cfg.restic.passwordFile != null;
          message = "nomarchy.services.restic.passwordFile must be set when restic is enabled.";
        }
      ];
      services.restic.backups.nomarchy = {
        repository = cfg.restic.repository;
        passwordFile = cfg.restic.passwordFile;
        paths = cfg.restic.paths;
        initialize = true; # create the repo on first run if it doesn't exist
        extraBackupArgs = [ "--exclude-caches" ];
        pruneOpts = [ "--keep-daily 7" "--keep-weekly 4" "--keep-monthly 6" ];
        timerConfig = {
          OnCalendar = "daily";
          Persistent = true; # catch up a missed run after downtime
        };
      };
    })
  ];
}
