# Home Manager wiring for DMS (imported by default.nix; lives under _private/ for import-tree).
{
  inputs,
  config,
  pkgs,
  lib,
  osConfig ? { },
  ...
}:
let
  home = config.home.homeDirectory;
  dmsEnabled = lib.attrByPath [ "chiasson" "desktop" "shell" ] null osConfig == "dms";
  dmsOs = lib.attrByPath [ "chiasson" "desktop" "shells" "dms" ] { } osConfig;
  hostName = lib.attrByPath [ "networking" "hostName" ] "nixos" osConfig;
  rebuildCommand =
    if (dmsOs.rebuildCommand or null) != null then
      dmsOs.rebuildCommand
    else
      [ "sudo" "nixos-rebuild" "switch" "--flake" ".#${hostName}" ];
  gpuTempEnabled = dmsOs.enableGpuTemp or true;
  hyprlandEnabled = lib.attrByPath [ "chiasson" "desktop" "hyprland" "enable" ] false osConfig;
  discordEnabled = lib.attrByPath [ "chiasson" "home" "apps" "discord" "enable" ] false config;
  ohMyPoshEnabled = config.programs.oh-my-posh.enable or false;
  obsidianSnippetDirs = map (v: v + "/snippets") (dmsOs.obsidianVaults or [ ]);
  matugenOutputDirs =
    lib.optionals hyprlandEnabled [ "${home}/.config/hypr" ]
    ++ lib.optionals ohMyPoshEnabled [ "${home}/.config/oh-my-posh" ]
    ++ lib.optionals discordEnabled [ "${home}/.config/vesktop/themes" ]
    ++ [
      "${home}/.config/qt6ct/colors"
      "${home}/.config/qt5ct/colors"
    ];
  ensureMatugenBody =
    lib.concatStringsSep "\n" (map (dir: "mkdir -p ${lib.escapeShellArg dir}") matugenOutputDirs)
    + lib.concatStringsSep "\n" (map (dir: ''
      snippetDir=${lib.escapeShellArg dir}
      parentDir="$(dirname "$snippetDir")"
      if [ -d "$snippetDir" ]; then
        :
      elif [ ! -d "$parentDir" ] || [ ! -w "$parentDir" ]; then
        echo "dms: skipping Obsidian snippet dir '$snippetDir' (parent missing or not writable)" >&2
      else
        mkdir -p "$snippetDir"
      fi
    '') obsidianSnippetDirs)
    + lib.optionalString ohMyPoshEnabled ''
      dest="$HOME/.config/oh-my-posh/theme.omp.json"
      template="$HOME/.config/matugen/templates/ohmyposh-theme.omp.json"
      if [ -L "$dest" ] || { [ -e "$dest" ] && ! [ -f "$dest" ]; }; then
        rm -f "$dest"
      fi
      if [ ! -f "$dest" ]; then
        cp -L "$template" "$dest"
      fi
    '';
  ensureMatugenOutputDirsScript = pkgs.writeShellScript "dms-ensure-matugen-output-dirs" ''
    set -euo pipefail
    ${ensureMatugenBody}
  '';

  dmsRestartOnceScript = pkgs.writeShellScript "dms-plugin-restart-once" (
    builtins.replaceStrings
      [ "@FLOCK@" "@SYSTEMCTL@" ]
      [
        "${pkgs.util-linux}/bin/flock"
        "${pkgs.systemd}/bin/systemctl"
      ]
      (builtins.readFile ../scripts/dms-restart-once.sh)
  );

  jsonFormat = pkgs.formats.json { };
  dmsConfigDir = "${config.xdg.configHome}/DankMaterialShell";
  defaultSeedDir = dmsOs.defaultSeedDir or null;

  dmsSeedSources =
    if defaultSeedDir != null then
      {
        "settings.json" = defaultSeedDir + "/settings.json";
      }
      // lib.optionalAttrs (builtins.pathExists (defaultSeedDir + "/plugin_settings.json")) {
        "plugin_settings.json" = defaultSeedDir + "/plugin_settings.json";
      }
    else
      {
        "settings.json" = jsonFormat.generate "dms-settings-seed.json" (
          import ./default-settings.nix {
            inherit lib gpuTempEnabled;
            extraRightBarWidgets = dmsOs.extraRightBarWidgets or [ ];
          }
        );
      };

  dmsSeedBody = lib.concatMapStringsSep "\n" (
    fileName:
    let
      dest = "${dmsConfigDir}/${fileName}";
      src = dmsSeedSources.${fileName};
    in
    ''
      dest=${lib.escapeShellArg dest}
      mkdir -p "$(dirname "$dest")"
      if [ -L "$dest" ]; then
        ${pkgs.coreutils}/bin/cp --remove-destination -L "$dest" "$dest"
      fi
      if [ ! -e "$dest" ]; then
        ${pkgs.coreutils}/bin/cp ${lib.escapeShellArg src} "$dest"
        chmod u+w "$dest"
      fi
    ''
  ) (lib.attrNames dmsSeedSources);
  dmsSeedConfigScript = pkgs.writeShellScript "dms-seed-config" ''
    set -euo pipefail
    ${dmsSeedBody}
  '';

  matugenConfigText = import ./matugen-config.nix {
    inherit
      lib
      home
      hyprlandEnabled
      ohMyPoshEnabled
      discordEnabled
      obsidianSnippetDirs
      ;
  };

  # Plugin registry auto-derived from flake inputs matching `dms-plugin-*`.
  # Adding a plugin now requires editing only `flake.nix`. The on-disk
  # plugin id (= path under ~/.config/DankMaterialShell/plugins/) is camelCase
  # per upstream convention; the kebab→camel transform matches it. The
  # `idExceptions` map carries the one plugin whose manifest id does not
  # follow the default rule.
  pluginInputs = lib.filterAttrs (n: _: lib.hasPrefix "dms-plugin-" n) inputs;

  # `dank-vault` → `dankVault`. `lib.splitString` yields ≥ 1 element so
  # `head parts` is safe; `map` runs over the tail only (no-op for
  # single-word names like `calculator`). `builtins.substring 1 (-1) p`
  # returns the rest of `p` from index 1, dropping the first char
  # (Nix treats a negative `len` as "rest of string from `start`").
  toCamel = s:
    let parts = lib.splitString "-" s; in
      builtins.head parts
      + lib.concatStrings (map (
        p: if p == "" then ""
          else lib.toUpper (builtins.substring 0 1 p)
            + builtins.substring 1 (-1) p
      ) (builtins.tail parts));

  # The `home-assistant` plugin ships its manifest under id
  # `homeAssistantMonitor` (not the default kebab→camel result).
  idExceptions = { "home-assistant" = "homeAssistantMonitor"; };

  # NB: don't rewrite as `idExceptions.${stripped} or toCamel stripped`.
  # Nix grammar: `or` binds tighter than function application, so the
  # `toCamel` and its arg parse as separate postfix args to the select.
  # When the lookup succeeds the resolved string is then applied as a
  # function, producing `attempt to call something which is not a function
  # but a string: "homeAssistantMonitor"`. Explicit `if/hasAttr` sidesteps
  # the parse entirely.
  camelMap = lib.mapAttrs' (n: src:
    let stripped = lib.removePrefix "dms-plugin-" n; in
    lib.nameValuePair
      (if builtins.hasAttr stripped idExceptions
       then idExceptions.${stripped}
       else toCamel stripped)
      src
  ) pluginInputs;

  # The official plugin pack bundles multiple QML apps. Ship only the
  # DankDesktopWeather subdir (verified by its manifest) to slim closure
  # and avoid overlapping with future dedicated `dms-plugin-*` flakes.
  thirdPartyPlugins =
    builtins.removeAttrs camelMap [ "official" ] // {
      dankDesktopWeather = inputs.dms-plugin-official + "/DankDesktopWeather";
    };

  # DMS reads plugins from ~/.config/DankMaterialShell/plugins/<id>/, so
  # we route each plugin source there via HM's `xdg.configFile.target`.
  # The attrset name is scoped under `dms-plugin-${id}` (not the bare
  # camelCase id) so unrelated HM modules can ship a same-named file
  # (e.g. `xdg.configFile.calculator`) without colliding with us.
  shipPlugin = id: src: {
    source = src;
    target = "DankMaterialShell/plugins/${id}";
    onChange = "${dmsRestartOnceScript}";
  };
in
{
  imports = [
    inputs.dms.homeModules.dank-material-shell
    inputs.nix-monitor.homeManagerModules.default
  ];

  config = lib.mkIf dmsEnabled (lib.mkMerge [
    (lib.mkIf (dmsOs.bundleThirdPartyPlugins or true) {
      xdg.configFile = lib.mapAttrs' (id: src: lib.nameValuePair
        "dms-plugin-${id}" (shipPlugin id src)) thirdPartyPlugins;
    })
    {
      programs.nix-monitor = {
        enable = true;
        inherit rebuildCommand;
        generationsCommand = [
          "bash"
          "-c"
          "readlink /nix/var/nix/profiles/system | cut -d- -f2"
        ];
      };

      home.packages = with pkgs; [
        kdePackages.qtdeclarative
        kdePackages.kirigami.unwrapped
        kdePackages.qtmultimedia
        kdePackages.sonnet
        tesseract
        mpv
        socat
        wl-clipboard
        jq
        cava
        curl
        libsecret
      ];

      programs.dank-material-shell = {
        enable = true;
        package = inputs.dms.packages.${pkgs.stdenv.hostPlatform.system}.dms-shell.override {
          extraQtPackages = with pkgs.kdePackages; [ qtwebsockets ];
        };
        systemd = {
          enable = true;
          restartIfChanged = true;
        };
        enableSystemMonitoring = true;
        dgop.package = inputs.dgop.packages.${pkgs.stdenv.hostPlatform.system}.default;
        enableVPN = true;
        enableDynamicTheming = true;
        enableAudioWavelength = true;
        enableCalendarEvents = false;
        # Empty on purpose: DMS owns ~/.config/DankMaterialShell/{settings,session}.json
        # at runtime (seeded by `home.activation.dmsSeedConfig`). Setting these
        # to non-empty here would clobber the runtime seed on every activation.
        settings = { };
        session = { };
      };

      # Surface bad obsidianVaults paths at build time instead of at first matugen run.
      # Each entry must point at the `<vault>/.obsidian` dir (matugen writes
      # `<vault>/.obsidian/snippets/matugen.css` from there).
      assertions = lib.mkIf ((dmsOs.obsidianVaults or [ ]) != [ ]) [
        {
          assertion = builtins.all (v: lib.hasSuffix "/.obsidian" v) (dmsOs.obsidianVaults or [ ]);
          message = "chiasson.desktop.shells.dms.obsidianVaults entries must end in '/.obsidian' (matugen writes <vault>/.obsidian/snippets/matugen.css). Example: /home/olivier/Documents/HomeLab/.obsidian";
        }
      ];

      home.activation.dmsSeedConfig = lib.hm.dag.entryAfter [ "writeBoundary" ] "${dmsSeedConfigScript}";

      systemd.user.services.dms.serviceConfig.ExecStartPre =
        lib.mkOrder 100 "${ensureMatugenOutputDirsScript}";

      xdg.configFile."matugen/config.toml".text = matugenConfigText;
      xdg.configFile."matugen/templates/dank-discord.css".source = ../templates/dank-discord.css;
      xdg.configFile."matugen/templates/hyprland-colors.conf".source = ../templates/hyprland-colors.conf;
      xdg.configFile."matugen/templates/ohmyposh-theme.omp.json".source = ../templates/ohmyposh-theme.omp.json;
      xdg.configFile."matugen/templates/obsidian-minimal-matugen-colors.css".source =
        ../templates/obsidian-minimal-matugen-colors.css;
    }
    (lib.mkIf ohMyPoshEnabled {
      programs.oh-my-posh = {
        useTheme = lib.mkForce null;
        configFile = lib.mkForce "${config.xdg.configHome}/oh-my-posh/theme.omp.json";
      };
    })
  ]);
}
