firefox: Add search engine options

I didn't default-enable bookmarks because enabling any home-manager
bookmark options will wipe your imperative bookmarks, so it's important
to make sure people know what they're getting into.

I also didn't default-enable extensions because I wanted to avoid
default-installing the DuckDuckGo privacy tools extension when this
feature was turned on, and because they are not necessary to use the
core search feature.

The images for NixOS searches are also too similar, it might be nice to
get some custom icons so we can tell at a glance which is is which

I considered adding NUR, but was unable to as they don't provide a
search page. This may be changed in this GitHub issue:
- https://github.com/nix-community/nur-search/issues/9

Change-Id: I70b54e2d98ecb89471ad5075518f67cc79038618
Reviewed-on: https://git.clicks.codes/c/Chimera/NixFiles/+/461
Tested-by: Skyler Grey <minion@clicks.codes>
Reviewed-by: Samuel Shuert <coded@clicks.codes>
diff --git a/homes/x86_64-linux/minion@greylag/default.nix b/homes/x86_64-linux/minion@greylag/default.nix
index 0e574ac..229e172 100644
--- a/homes/x86_64-linux/minion@greylag/default.nix
+++ b/homes/x86_64-linux/minion@greylag/default.nix
@@ -23,25 +23,6 @@
     greylag
   '';
 
-  programs.firefox.profiles.chimera.search = {
-    engines = {
-      "Kagi" = {
-        urls = [
-          { template = "https://kagi.com/search?q={searchTerms}"; }
-          {
-            template = "https://kagi.com/api/autosuggest?q={searchTerms}";
-            type = "application/x-suggestions+json";
-          }
-        ];
-        iconUpdateURL = "https://assets.kagi.com/v2/favicon-32x32.png";
-        updateInterval = 24 * 60 * 60 * 1000;
-      };
-    };
-    order = [ "Kagi" ];
-    default = "Kagi";
-    force = true;
-  };
-
   programs.gpg.scdaemonSettings = {
     reader-port = "Yubico Yubi";
   };
@@ -104,9 +85,24 @@
         reactDevTools.enable = true;
         adnauseam.enable = true;
       };
+      search = {
+        enable = true;
+        extensions.enable = true;
+        bookmarks.enable = true;
+        engines = [
+          "Kagi"
+          "MDN"
+          "NixOS Options"
+          "NixOS Packages"
+          "Home-Manager Options"
+          "Noogle"
+          "GitHub"
+          "Arch Wiki"
+          "Gentoo Wiki"
+        ];
+      };
       extraExtensions = [
         config.nur.repos.rycee.firefox-addons.sidebery
-        config.nur.repos.rycee.firefox-addons.kagi-search
       ];
     };
 
diff --git a/modules/home/browser/firefox/search/default.nix b/modules/home/browser/firefox/search/default.nix
new file mode 100644
index 0000000..d02aa70
--- /dev/null
+++ b/modules/home/browser/firefox/search/default.nix
@@ -0,0 +1,245 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}: let
+  possibleEngines = [
+    "Arch Wiki"
+    "Bing"
+    "DuckDuckGo"
+    "eBay"
+    "Gentoo Wiki"
+    "GitHub"
+    "Google"
+    "Home-Manager Options"
+    "Kagi"
+    "MDN"
+    "NixOS Options"
+    "NixOS Packages"
+    "Noogle"
+    "Wikipedia (en)"
+  ];
+in {
+  options.chimera.browser.firefox.search = {
+    enable = lib.mkEnableOption "Let Chimera control your firefox search engines";
+    extensions.enable = lib.mkEnableOption "Install extensions relating to your chosen search engines";
+    bookmarks.enable = lib.mkEnableOption "Add bookmarks to quickly jump to your chosen search engines. CAUTION: This option will overwrite (reset) any imperatively-added bookmarks";
+    engines = lib.mkOption {
+      type = lib.types.listOf (lib.types.enum possibleEngines);
+      description = ''
+        A list of search engines you want to enable. Any enabled engines will have the following Chimera customizations added:
+        - They will be added to Firefox (if they are not already a default firefox search engine)
+        - They will have an alias given[1], you can type "{alias} query" to search query with the engine
+        - If you have enabled config.chimera.browser.firefox.search.installExtensions, they will have their respective extension
+          installed (if one exists), e.g. enabling Kagi will install the Kagi extension for private browsing support[2]
+        - If you have enabled config.chimera.browser.firefox.search.addBookmarks, they will have a shortcut added so you can type
+          only "{alias}" and go to their homepage.
+
+        The search engines will be ordered according to the order you give them in this list. The first one will be set as your
+        default search engine.
+
+        1:
+          Arch Wiki -> arch
+          Bing -> bing
+          DuckDuckGo -> ddg
+          eBay -> ebay
+          Gentoo Wiki -> gentoo
+          GitHub -> gh
+          Google -> google
+          Home-Manager Options -> hm
+          Kagi -> kagi
+          MDN -> mdn
+          NixOS Options -> opts
+          NixOS Packages -> pkgs
+          Noogle -> lib
+          Wikipedia (en) -> wiki
+        2:
+          DuckDuckGo installs https://addons.mozilla.org/en-US/firefox/addon/duckduckgo-for-firefox
+          Kagi installs https://addons.mozilla.org/en-US/firefox/addon/kagi-search-for-firefox/
+      '';
+      default = [
+        "DuckDuckGo"
+        "MDN"
+        "NixOS Options"
+        "NixOS Packages"
+        "Home-Manager Options"
+        "Noogle"
+        "GitHub"
+      ];
+      example = [
+        "Kagi"
+        "MDN"
+        "NixOS Options"
+        "NixOS Packages"
+        "Home-Manager Options"
+        "Noogle"
+        "GitHub"
+        "Arch Wiki"
+        "Gentoo Wiki"
+      ];
+    };
+  };
+
+  config =
+    let
+      engineData = {
+        "Arch Wiki" = {
+          urls = [ { template = "https://wiki.archlinux.org/index.php?search={searchTerms}"; } ];
+          iconUpdateURL = "https://wiki.archlinux.org/favicon.ico";
+          updateInterval = 24 * 60 * 60 * 1000;
+          definedAliases = [ "arch" ];
+          homepage = "https://wiki.archlinux.org/";
+        };
+        "Bing" = {
+          homepage = "https://bing.com";
+          metaData.alias = "bing";
+        };
+        "DuckDuckGo" = {
+          homepage = "https://duckduckgo.com";
+          metaData.alias = "ddg";
+          extraExtensions = [ config.nur.repos.rycee.firefox-addons.duckduckgo-privacy-essentials ];
+        };
+        "eBay" = {
+          homepage = "https://ebay.com";
+          metaData.alias = "ebay";
+        };
+        "Gentoo Wiki" = {
+          urls = [ { template = "https://wiki.gentoo.org/index.php?search={searchTerms}"; } ];
+          iconUpdateURL = "https://www.gentoo.org/favicon.ico";
+          updateInterval = 24 * 60 * 60 * 1000;
+          definedAliases = [ "gentoo" ];
+          homepage = "https://wiki.gentoo.org";
+        };
+        "GitHub" = {
+          urls = [ { template = "https://github.com/search?q={searchTerms}"; } ];
+          iconUpdateURL = "https://github.com/favicon.ico";
+          updateInterval = 24 * 60 * 60 * 1000;
+          definedAliases = [ "gh" ];
+          homepage = "https://github.com";
+        };
+        "Google" = {
+          homepage = "https://google.com";
+          metaData.alias = "google";
+        };
+        "Home-Manager Options" = {
+          urls = [
+            { template = "https://mipmip.github.io/home-manager-option-search/?query={searchTerms}"; }
+          ];
+          iconUpdateURL = "https://mipmip.github.io/home-manager-option-search/images/favicon.png";
+          updateInterval = 24 * 60 * 60 * 1000;
+          definedAliases = [ "hm" ];
+          homepage = "https://mipmip.github.io/home-manager-option-search/";
+        };
+        "Kagi" = {
+          urls = [
+            { template = "https://kagi.com/search?q={searchTerms}"; }
+            {
+              template = "https://kagi.com/api/autosuggest?q={searchTerms}";
+              type = "application/x-suggestions+json";
+            }
+          ];
+          iconUpdateURL = "https://assets.kagi.com/v2/favicon-32x32.png";
+          updateInterval = 24 * 60 * 60 * 1000;
+          homepage = "https://kagi.com";
+          definedAliases = [ "kagi" ];
+          extraExtensions = [ config.nur.repos.rycee.firefox-addons.kagi-search ];
+        };
+        "MDN" = {
+          urls = [ { template = "https://developer.mozilla.org/en-US/search?q={searchTerms}"; } ];
+          iconUpdateURL = "https://developer.mozilla.org/favicon.ico";
+          updateInterval = 24 * 60 * 60 * 1000;
+          homepage = "https://developer.mozilla.org";
+          definedAliases = [ "mdn" ];
+        };
+        "NixOS Options" = {
+          urls = [ { template = "https://search.nixos.org/options?channel=unstable&query={searchTerms}"; } ];
+          iconUpdateURL = "https://nixos.org/logo/nix-wiki.png";
+          updateInterval = 24 * 60 * 60 * 1000;
+          homepage = "https://search.nixos.org/options?channel=unstable";
+          definedAliases = [ "opts" ];
+        };
+        "NixOS Packages" = {
+          urls = [ { template = "https://search.nixos.org/packages?channel=unstable&query={searchTerms}"; } ];
+          iconUpdateURL = "https://nixos.org/logo/nix-wiki.png";
+          updateInterval = 24 * 60 * 60 * 1000;
+          homepage = "https://search.nixos.org/packages?channel=unstable";
+          definedAliases = [ "pkgs" ];
+        };
+        "Noogle" = {
+          urls = [ { template = "https://noogle.dev/q?term={searchTerms}"; } ];
+          iconUpdateURL = "https://noogle.dev/favicon.png";
+          updateInterval = 24 * 60 * 60 * 1000;
+          homepage = "https://noogle.dev";
+          definedAliases = [ "lib" ];
+        };
+        "Wikipedia (en)" = {
+          homepage = "https://en.wikipedia.org";
+          metaData.alias = "wiki";
+        };
+      };
+      firefoxDefaultEngines = [
+        "Bing"
+        "Google"
+        "eBay"
+        "DuckDuckGo"
+        "Wikipedia (en)"
+      ];
+      calculated = lib.pipe engineData [
+        (builtins.mapAttrs (
+          name: value: {
+            engines.${name} =
+              lib.filterAttrs
+                (
+                  option: _:
+                  !(builtins.elem option [
+                    "homepage"
+                    "extraExtensions"
+                  ])
+                )
+                value;
+            extensions = if builtins.hasAttr "extraExtensions" value then value.extraExtensions else [ ];
+            bookmarks =
+              if builtins.hasAttr "homepage" value then
+                [
+                  {
+                    inherit name;
+                    keyword = if builtins.hasAttr "definedAliases" value
+                              then builtins.elemAt value.definedAliases 0
+                              else value.metaData.alias;
+                    url = value.homepage;
+                  }
+                ]
+              else
+                [ ];
+          }
+        ))
+        builtins.attrValues
+        (lib.zipAttrsWithNames ["engines" "extensions" "bookmarks"] (name: values: if name == "engines"
+                                                                                   then lib.attrsets.mergeAttrsList values
+                                                                                   else builtins.concatLists values))
+      ];
+      removedEngines = lib.pipe possibleEngines [
+        (builtins.filter (engine: !(builtins.elem engine config.chimera.browser.firefox.search.engines)))
+        (map (engine: {
+          name = engine;
+          value.metaData.hidden = true;
+        }))
+        builtins.listToAttrs
+      ]; # We need to hide all the engines that we aren't selecting, because engines that we unset are not removed
+    in
+    lib.mkIf config.chimera.browser.firefox.search.enable {
+      programs.firefox.profiles.chimera = {
+        search = {
+          engines = calculated.engines // removedEngines;
+          order = config.chimera.browser.firefox.search.engines;
+          default = lib.mkIf (builtins.length config.chimera.browser.firefox.search.engines > 0) (
+            builtins.elemAt config.chimera.browser.firefox.search.engines 0
+          );
+          force = true;
+        };
+        extensions = lib.mkIf config.chimera.browser.firefox.search.extensions.enable calculated.extensions;
+        bookmarks = lib.mkIf config.chimera.browser.firefox.search.bookmarks.enable calculated.bookmarks;
+      };
+    };
+}