{
  modulePath,
  name,
  description ? null,
  wrappedPackageName ? null,
  unwrappedPackageName ? null,
  platforms,
  visible ? false,
  enableBookmarks ? true,
}:
{
  config,
  lib,
  pkgs,
  ...
}:
let
  inherit (lib)
    attrValues
    concatStringsSep
    length
    literalExpression
    mapAttrsToList
    mkIf
    mkMerge
    mkOption
    optionalString
    optional
    setAttrByPath
    types
    ;
  inherit (pkgs.stdenv.hostPlatform) isDarwin;

  appName = name;

  moduleName = concatStringsSep "." modulePath;

  cfg = lib.getAttrFromPath modulePath config;

  jsonFormat = pkgs.formats.json { };

  supportedPlatforms = lib.flatten (lib.attrVals (lib.attrNames platforms) lib.platforms);

  isWrapped = lib.versionAtLeast config.home.stateVersion "19.09" && wrappedPackageName != null;

  defaultPackageName = if isWrapped then wrappedPackageName else unwrappedPackageName;

  packageName = if wrappedPackageName != null then wrappedPackageName else unwrappedPackageName;

  # The extensions path shared by all profiles; will not be supported
  # by future browser versions.
  extensionPath = "extensions/{ec8030f7-c20a-464f-9b0e-13a3a9e97384}";

  profiles =
    lib.flip lib.mapAttrs' cfg.profiles (
      _: profile:
      lib.nameValuePair "Profile${toString profile.id}" {
        Name = profile.name;
        Path = if isDarwin then "Profiles/${profile.path}" else profile.path;
        IsRelative = 1;
        Default = if profile.isDefault then 1 else 0;
      }
    )
    // {
      General =
        {
          StartWithLastProfile = 1;
        }
        // lib.optionalAttrs (cfg.profileVersion != null) {
          Version = cfg.profileVersion;
        };
    };

  profilesIni = lib.generators.toINI { } profiles;

  userPrefValue =
    pref:
    builtins.toJSON (
      if lib.isBool pref || lib.isInt pref || lib.isString pref then pref else builtins.toJSON pref
    );

  mkUserJs =
    prePrefs: prefs: extraPrefs: bookmarksFile: extensions:
    let
      prefs' =
        lib.optionalAttrs (bookmarksFile != null) {
          "browser.bookmarks.file" = toString bookmarksFile;
          "browser.places.importBookmarksHTML" = true;
        }
        // lib.optionalAttrs (extensions != { }) {
          "extensions.webextensions.ExtensionStorageIDB.enabled" = false;
        }
        // prefs;
    in
    ''
      // Generated by Home Manager.

      ${prePrefs}

      ${lib.concatStrings (
        mapAttrsToList (name: value: ''
          user_pref("${name}", ${userPrefValue value});
        '') prefs'
      )}

      ${extraPrefs}
    '';

  mkContainersJson =
    containers:
    let
      containerToIdentity = _: container: {
        userContextId = container.id;
        name = container.name;
        icon = container.icon;
        color = container.color;
        public = true;
      };
    in
    ''
      ${builtins.toJSON {
        version = 5;
        lastUserContextId = lib.foldlAttrs (
          acc: _: value:
          if value.id > acc then value.id else acc
        ) 0 containers;
        identities = mapAttrsToList containerToIdentity containers ++ [
          {
            userContextId = 4294967294; # 2^32 - 2
            name = "userContextIdInternal.thumbnail";
            icon = "";
            color = "";
            accessKey = "";
            public = false;
          }
          {
            userContextId = 4294967295; # 2^32 - 1
            name = "userContextIdInternal.webextStorageLocal";
            icon = "";
            color = "";
            accessKey = "";
            public = false;
          }
        ];
      }}
    '';

  mkNoDuplicateAssertion =
    entities: entityKind:
    (
      let
        # Return an attribute set with entity IDs as keys and a list of
        # entity names with corresponding ID as value. An ID is present in
        # the result only if more than one entity has it. The argument
        # entities is a list of AttrSet of one id/name pair.
        findDuplicateIds =
          entities: lib.filterAttrs (_entityId: entityNames: length entityNames != 1) (lib.zipAttrs entities);

        duplicates = findDuplicateIds (
          mapAttrsToList (entityName: entity: { "${toString entity.id}" = entityName; }) entities
        );

        mkMsg = entityId: entityNames: "  - ID ${entityId} is used by " + concatStringsSep ", " entityNames;
      in
      {
        assertion = duplicates == { };
        message =
          ''
            Must not have a ${appName} ${entityKind} with an existing ID but
          ''
          + concatStringsSep "\n" (mapAttrsToList mkMsg duplicates);
      }
    );

  wrapPackage =
    package:
    let
      # The configuration expected by the Firefox wrapper.
      fcfg = {
        enableGnomeExtensions = cfg.enableGnomeExtensions;
      };

      # A bit of hackery to force a config into the wrapper.
      browserName = package.browserName or (builtins.parseDrvName package.name).name;

      # The configuration expected by the Firefox wrapper builder.
      bcfg = setAttrByPath [ browserName ] fcfg;
    in
    if package == null then
      null
    else if isWrapped then
      package.override (old: {
        cfg = old.cfg or { } // fcfg;
        extraPolicies = (old.extraPolicies or { }) // cfg.policies;
        pkcs11Modules = (old.pkcs11Modules or [ ]) ++ cfg.pkcs11Modules;
      })
    else
      (pkgs.wrapFirefox.override { config = bcfg; }) package { };

  bookmarkTypes = import ./profiles/bookmark-types.nix { inherit lib; };
in
{
  options = setAttrByPath modulePath {
    enable = mkOption {
      type = types.bool;
      default = false;
      example = true;
      description = ''
        Whether to enable ${appName}.${optionalString (description != null) " ${description}"}
        ${optionalString (!visible) "See `${moduleName}` for more configuration options."}
      '';
    };

    package = mkOption {
      inherit visible;
      type = with types; nullOr package;
      default = pkgs.${defaultPackageName};
      defaultText = literalExpression "pkgs.${packageName}";
      example = literalExpression ''
        pkgs.${packageName}.override {
          # See nixpkgs' firefox/wrapper.nix to check which options you can use
          nativeMessagingHosts = [
            # Gnome shell native connector
            pkgs.gnome-browser-connector
            # Tridactyl native connector
            pkgs.tridactyl-native
          ];
        }
      '';
      description = ''
        The ${appName} package to use. If state version ≥ 19.09 then
        this should be a wrapped ${appName} package. For earlier state
        versions it should be an unwrapped ${appName} package.
        Set to `null` to disable installing ${appName}.
      '';
    };

    languagePacks = mkOption {
      type = types.listOf types.str;
      default = [ ];
      description = ''
        The language packs to install. Available language codes can be found
        on the releases page:
        `https://releases.mozilla.org/pub/firefox/releases/''${version}/linux-x86_64/xpi/`,
        replacing `''${version}` with the version of ${appName} you have.
      '';
      example = [
        "en-GB"
        "de"
      ];
    };

    name = mkOption {
      internal = true;
      type = types.str;
      default = name;
      example = "Firefox";
      description = "The name of the browser.";
    };

    wrappedPackageName = mkOption {
      internal = true;
      type = with types; nullOr str;
      default = wrappedPackageName;
      description = "Name of the wrapped browser package.";
    };

    darwinAppName = mkOption {
      internal = true;
      type = types.str;
      default =
        lib.toUpper (lib.substring 0 1 cfg.wrappedPackageName)
        + lib.toLower (
          lib.substring 1 ((lib.stringLength cfg.wrappedPackageName) - 1) cfg.wrappedPackageName
        );
      description = "Name of browser app on Darwin.";
    };

    profilesPath = mkOption {
      internal = true;
      type = types.str;
      default = if isDarwin then "${cfg.configPath}/Profiles" else cfg.configPath;
      description = "Path to profiles.";
    };

    vendorPath = mkOption {
      internal = true;
      type = with types; nullOr str;
      default = null;
      example = ".mozilla";
      description = "Directory containing the native messaging hosts directory.";
    };

    configPath = mkOption {
      internal = true;
      type = types.str;
      default = with platforms; if isDarwin then darwin.configPath else linux.configPath;
      example = ".mozilla/firefox";
      description = "Directory containing the ${appName} configuration files.";
    };

    nativeMessagingHosts = mkOption {
      inherit visible;
      type = types.listOf types.package;
      default = [ ];
      description = ''
        Additional packages containing native messaging hosts that should be
        made available to ${appName} extensions.
      '';
    };

    finalPackage = mkOption {
      inherit visible;
      type = with types; nullOr package;
      readOnly = true;
      description = "Resulting ${appName} package.";
    };

    policies = lib.optionalAttrs (wrappedPackageName != null) (mkOption {
      inherit visible;
      type = types.attrsOf jsonFormat.type;
      default = { };
      description = "[See list of policies](https://mozilla.github.io/policy-templates/).";
      example = {
        DefaultDownloadDirectory = "\${home}/Downloads";
        BlockAboutConfig = true;
      };
    });

    profileVersion = mkOption {
      internal = true;
      type = types.nullOr types.ints.unsigned;
      default = if isDarwin then null else 2;
      description = "profile version, set null for nix-darwin";
    };

    profiles = mkOption {
      inherit visible;
      type = types.attrsOf (
        types.submodule (
          { config, name, ... }:
          {
            imports = [ (pkgs.path + "/nixos/modules/misc/assertions.nix") ];

            options = {
              name = mkOption {
                type = types.str;
                default = name;
                description = "Profile name.";
              };

              id = mkOption {
                type = types.ints.unsigned;
                default = 0;
                description = ''
                  Profile ID. This should be set to a unique number per profile.
                '';
              };

              preConfig = mkOption {
                type = types.lines;
                default = "";
                description = ''
                  Extra preferences to add to {file}`user.js`, before
                  [](#opt-programs.firefox.profiles._name_.settings).

                  Use [](#opt-programs.firefox.profiles._name_.extraConfig), unless
                  you want to overwrite in
                  [](#opt-programs.firefox.profiles._name_.settings), then use this
                  option.
                '';
              };

              settings = mkOption {
                type = types.attrsOf (
                  jsonFormat.type
                  // {
                    description = "${appName} preference (int, bool, string, and also attrs, list, float as a JSON string)";
                  }
                );
                default = { };
                example = literalExpression ''
                  {
                    "browser.startup.homepage" = "https://nixos.org";
                    "browser.search.region" = "GB";
                    "browser.search.isUS" = false;
                    "distribution.searchplugins.defaultLocale" = "en-GB";
                    "general.useragent.locale" = "en-GB";
                    "browser.bookmarks.showMobileBookmarks" = true;
                    "browser.newtabpage.pinned" = [{
                      title = "NixOS";
                      url = "https://nixos.org";
                    }];
                  }
                '';
                description = ''
                  Attribute set of ${appName} preferences.

                  ${appName} only supports int, bool, and string types for
                  preferences, but home-manager will automatically
                  convert all other JSON-compatible values into strings.
                '';
              };

              extraConfig = mkOption {
                type = types.lines;
                default = "";
                description = ''
                  Extra preferences to add to {file}`user.js`.
                '';
              };

              userChrome = mkOption {
                type = types.oneOf [
                  types.lines
                  types.path
                ];
                default = "";
                description = "Custom ${appName} user chrome CSS.";
                example = ''
                  /* Hide tab bar in FF Quantum */
                  @-moz-document url(chrome://browser/content/browser.xul), url(chrome://browser/content/browser.xhtml) {
                    #TabsToolbar {
                      visibility: collapse !important;
                      margin-bottom: 21px !important;
                    }

                    #sidebar-box[sidebarcommand="treestyletab_piro_sakura_ne_jp-sidebar-action"] #sidebar-header {
                      visibility: collapse !important;
                    }
                  }
                '';
              };

              userContent = mkOption {
                type = types.oneOf [
                  types.lines
                  types.path
                ];
                default = "";
                description = "Custom ${appName} user content CSS.";
                example = ''
                  /* Hide scrollbar in FF Quantum */
                  *{scrollbar-width:none !important}
                '';
              };

              bookmarks = mkOption {
                type = (
                  types.coercedTo bookmarkTypes.settingsType
                    (
                      bookmarks:
                      if bookmarks != { } then
                        lib.warn
                          ''
                            ${cfg.name} bookmarks have been refactored into a submodule that now explicitly require a 'force' option to be enabled.

                            Replace:

                            ${moduleName}.profiles.${name}.bookmarks = [ ... ];

                            With:

                            ${moduleName}.profiles.${name}.bookmarks = {
                              force = true;
                              settings = [ ... ];
                            };
                          ''
                          {
                            force = true;
                            settings = bookmarks;
                          }
                      else
                        { }
                    )
                    (
                      types.submodule (
                        { config, ... }:
                        import ./profiles/bookmarks.nix {
                          inherit config lib pkgs;
                          modulePath = modulePath ++ [
                            "profiles"
                            name
                            "bookmarks"
                          ];
                        }
                      )
                    )
                );
                default = { };
                internal = !enableBookmarks;
                description = "Declarative bookmarks.";
              };

              path = mkOption {
                type = types.str;
                default = name;
                description = "Profile path.";
              };

              isDefault = mkOption {
                type = types.bool;
                default = config.id == 0;
                defaultText = "true if profile ID is 0";
                description = "Whether this is a default profile.";
              };

              search = mkOption {
                type = types.submodule (
                  args:
                  import ./profiles/search.nix {
                    inherit (args) config;
                    inherit lib pkgs appName;
                    package = cfg.finalPackage;
                    modulePath = modulePath ++ [
                      "profiles"
                      name
                      "search"
                    ];
                    profilePath = config.path;
                  }
                );
                default = { };
                description = "Declarative search engine configuration.";
              };

              containersForce = mkOption {
                type = types.bool;
                default = false;
                description = ''
                  Whether to force replace the existing containers configuration.
                  This is recommended since ${appName} will replace the symlink on
                  every launch, but note that you'll lose any existing configuration
                  by enabling this.
                '';
              };

              containers = mkOption {
                type = types.attrsOf (
                  types.submodule (
                    { name, ... }:
                    {
                      options = {
                        name = mkOption {
                          type = types.str;
                          default = name;
                          description = "Container name, e.g., shopping.";
                        };

                        id = mkOption {
                          type = types.ints.unsigned;
                          default = 0;
                          description = ''
                            Container ID. This should be set to a unique number per container in this profile.
                          '';
                        };

                        # List of colors at
                        # https://searchfox.org/mozilla-central/rev/5ad226c7379b0564c76dc3b54b44985356f94c5a/toolkit/components/extensions/parent/ext-contextualIdentities.js#32
                        color = mkOption {
                          type = types.enum [
                            "blue"
                            "turquoise"
                            "green"
                            "yellow"
                            "orange"
                            "red"
                            "pink"
                            "purple"
                            "toolbar"
                          ];
                          default = "pink";
                          description = "Container color.";
                        };

                        icon = mkOption {
                          type = types.enum [
                            "briefcase"
                            "cart"
                            "circle"
                            "dollar"
                            "fence"
                            "fingerprint"
                            "gift"
                            "vacation"
                            "food"
                            "fruit"
                            "pet"
                            "tree"
                            "chill"
                          ];
                          default = "fruit";
                          description = "Container icon.";
                        };
                      };
                    }
                  )
                );
                default = { };
                example = {
                  "shopping" = {
                    id = 1;
                    color = "blue";
                    icon = "cart";
                  };
                  "dangerous" = {
                    id = 2;
                    color = "red";
                    icon = "fruit";
                  };
                };
                description = ''
                  Attribute set of container configurations. See
                  [Multi-Account
                  Containers](https://support.mozilla.org/en-US/kb/containers)
                  for more information.
                '';
              };
              extensions = mkOption {
                type =
                  types.coercedTo (types.listOf types.package)
                    (packages: {
                      packages = mkIf (builtins.length packages > 0) (
                        lib.warn ''
                          In order to support declarative extension configuration,
                          extension installation has been moved from
                          ${moduleName}.profiles.<profile>.extensions
                          to
                          ${moduleName}.profiles.<profile>.extensions.packages
                        '' packages
                      );
                    })
                    (
                      types.submodule {
                        options = {
                          packages = mkOption {
                            type = types.listOf types.package;
                            default = [ ];
                            example = literalExpression ''
                              with pkgs.nur.repos.rycee.firefox-addons; [
                                privacy-badger
                              ]
                            '';
                            description = ''
                              List of ${name} add-on packages to install for this profile.
                              Some pre-packaged add-ons are accessible from the Nix User Repository.
                              Once you have NUR installed run

                              ```console
                              $ nix-env -f '<nixpkgs>' -qaP -A nur.repos.rycee.firefox-addons
                              ```

                              to list the available ${name} add-ons.

                              Note that it is necessary to manually enable these extensions
                              inside ${name} after the first installation.

                              To automatically enable extensions add
                              `"extensions.autoDisableScopes" = 0;`
                              to
                              [{option}`${moduleName}.profiles.<profile>.settings`](#opt-${moduleName}.profiles._name_.settings)
                            '';
                          };

                          force = mkOption {
                            description = ''
                              Whether to override all previous firefox settings.

                              This is required when using `settings`.
                            '';
                            default = false;
                            example = true;
                            type = types.bool;
                          };

                          settings = mkOption {
                            default = { };
                            example = literalExpression ''
                              {
                                # Example with uBlock origin's extensionID
                                "uBlock0@raymondhill.net".settings = {
                                  selectedFilterLists = [
                                    "ublock-filters"
                                    "ublock-badware"
                                    "ublock-privacy"
                                    "ublock-unbreak"
                                    "ublock-quick-fixes"
                                  ];
                                };

                                # Example with Stylus' UUID-form extensionID
                                "{7a7a4a92-a2a0-41d1-9fd7-1e92480d612d}".settings = {
                                  dbInChromeStorage = true; # required for Stylus
                                }
                              }
                            '';
                            description = ''
                              Attribute set of options for each extension.
                              The keys of the attribute set consist of the ID of the extension
                              or its UUID wrapped in curly braces.
                            '';
                            type = types.attrsOf (
                              types.submodule {
                                options = {
                                  settings = mkOption {
                                    type = types.attrsOf jsonFormat.type;
                                    description = "Json formatted options for the specified extensionID";
                                  };
                                  force = mkOption {
                                    type = types.bool;
                                    default = false;
                                    example = true;
                                    description = ''
                                      Forcibly override any existing configuration for
                                      this extension.
                                    '';
                                  };
                                };
                              }
                            );
                          };
                        };
                      }
                    );
                default = { };
                description = ''
                  Submodule for installing and configuring extensions.
                '';
                example = literalExpression ''
                  {
                    packages = with pkgs.nur.repos.rycee.firefox-addons; [
                      ublock-origin
                    ];
                    settings."uBlock0@raymondhill.net".settings = {
                      selectedFilterLists = [
                        "ublock-filters"
                        "ublock-badware"
                        "ublock-privacy"
                        "ublock-unbreak"
                        "ublock-quick-fixes"
                      ];
                    };
                  }
                '';
              };
            };

            config = {
              assertions = [
                (mkNoDuplicateAssertion config.containers "container")
                {
                  assertion = config.extensions.settings == { } || config.extensions.force;
                  message = ''
                    Using '${
                      lib.showAttrPath (
                        modulePath
                        ++ [
                          "profiles"
                          config.name
                          "extensions"
                          "settings"
                        ]
                      )
                    }' will override all previous extensions settings.
                    Enable '${
                      lib.showAttrPath (
                        modulePath
                        ++ [
                          "profiles"
                          config.name
                          "extensions"
                          "force"
                        ]
                      )
                    }' to acknowledge this.
                  '';
                }
              ] ++ config.bookmarks.assertions;
            };
          }
        )
      );
      default = { };
      description = "Attribute set of ${appName} profiles.";
    };

    enableGnomeExtensions = mkOption {
      inherit visible;
      type = types.bool;
      default = false;
      description = ''
        Whether to enable the GNOME Shell native host connector. Note, you
        also need to set the NixOS option
        `services.gnome.gnome-browser-connector.enable` to
        `true`.
      '';
    };

    pkcs11Modules = mkOption {
      type = types.listOf types.package;
      default = [ ];
      description = ''
        Additional packages to be loaded as PKCS #11 modules in Firefox.
      '';
    };
  };

  config = mkIf cfg.enable (
    {
      assertions = [
        (lib.hm.assertions.assertPlatform moduleName pkgs supportedPlatforms)

        (
          let
            defaults = lib.catAttrs "name" (lib.filter (a: a.isDefault) (attrValues cfg.profiles));
          in
          {
            assertion = cfg.profiles == { } || length defaults == 1;
            message =
              "Must have exactly one default ${appName} profile but found "
              + toString (length defaults)
              + optionalString (length defaults > 1) (", namely " + concatStringsSep ", " defaults);
          }
        )

        (
          let
            getContainers =
              profiles: lib.flatten (mapAttrsToList (_: value: (attrValues value.containers)) profiles);

            findInvalidContainerIds =
              profiles: lib.filter (container: container.id >= 4294967294) (getContainers profiles);
          in
          {
            assertion = cfg.profiles == { } || length (findInvalidContainerIds cfg.profiles) == 0;
            message = "Container id must be smaller than 4294967294 (2^32 - 2)";
          }
        )

        {
          assertion = cfg.languagePacks == [ ] || cfg.package != null;
          message = ''
            '${moduleName}.languagePacks' requires '${moduleName}.package'
            to be set to a non-null value.
          '';
        }

        (mkNoDuplicateAssertion cfg.profiles "profile")
      ] ++ (lib.concatMap (profile: profile.assertions) (attrValues cfg.profiles));

      warnings =
        optional (cfg.enableGnomeExtensions or false) ''
          Using '${moduleName}.enableGnomeExtensions' has been deprecated and
          will be removed in the future. Please change to overriding the package
          configuration using '${moduleName}.package' instead. You can refer to
          its example for how to do this.
        ''
        ++ optional (cfg.vendorPath != null) ''
          Using '${moduleName}.vendorPath' has been deprecated and
          will be removed in the future. Native messaging hosts will function normally without specifying this path.
        '';

      home.packages = lib.optional (cfg.finalPackage != null) cfg.finalPackage;

      mozilla.firefoxNativeMessagingHosts =
        cfg.nativeMessagingHosts
        # package configured native messaging hosts (entire browser actually)
        ++ (lib.optional (cfg.finalPackage != null) cfg.finalPackage);

      home.file = mkMerge (
        [
          {
            "${cfg.configPath}/profiles.ini" = mkIf (cfg.profiles != { }) { text = profilesIni; };
          }
        ]
        ++ lib.flip mapAttrsToList cfg.profiles (
          _: profile:
          # Merge the regular profile settings with extension settings
          mkMerge (
            [
              {
                "${cfg.profilesPath}/${profile.path}/.keep".text = "";

                "${cfg.profilesPath}/${profile.path}/chrome/userChrome.css" = mkIf (profile.userChrome != "") (
                  let
                    key = if builtins.isString profile.userChrome then "text" else "source";
                  in
                  {
                    "${key}" = profile.userChrome;
                  }
                );

                "${cfg.profilesPath}/${profile.path}/chrome/userContent.css" = mkIf (profile.userContent != "") (
                  let
                    key = if builtins.isString profile.userContent then "text" else "source";
                  in
                  {
                    "${key}" = profile.userContent;
                  }
                );

                "${cfg.profilesPath}/${profile.path}/user.js" =
                  mkIf
                    (
                      profile.preConfig != ""
                      || profile.settings != { }
                      || profile.extraConfig != ""
                      || profile.bookmarks.configFile != null
                    )
                    {
                      text =
                        mkUserJs profile.preConfig profile.settings profile.extraConfig profile.bookmarks.configFile
                          profile.extensions.settings;
                    };

                "${cfg.profilesPath}/${profile.path}/containers.json" = mkIf (profile.containers != { }) {
                  text = mkContainersJson profile.containers;
                  force = profile.containersForce;
                };

                "${cfg.profilesPath}/${profile.path}/search.json.mozlz4" = mkIf (profile.search.enable) {
                  enable = profile.search.enable;
                  force = profile.search.force;
                  source = profile.search.file;
                };

                "${cfg.profilesPath}/${profile.path}/extensions" = mkIf (profile.extensions.packages != [ ]) {
                  source =
                    let
                      extensionsEnvPkg = pkgs.buildEnv {
                        name = "hm-firefox-extensions";
                        paths = profile.extensions.packages;
                      };
                    in
                    "${extensionsEnvPkg}/share/mozilla/${extensionPath}";
                  recursive = true;
                  force = true;
                };
              }
            ]
            ++
              # Add extension settings as separate attributes
              optional (profile.extensions.settings != { }) (
                mkMerge (
                  mapAttrsToList (name: settingConfig: {
                    "${cfg.profilesPath}/${profile.path}/browser-extension-data/${name}/storage.js" = {
                      force = settingConfig.force || profile.extensions.force;
                      text = lib.generators.toJSON { } settingConfig.settings;
                    };
                  }) profile.extensions.settings
                )
              )
          )
        )
      );
    }
    // setAttrByPath modulePath {
      finalPackage = wrapPackage cfg.package;

      policies = {
        ExtensionSettings = lib.mkIf (cfg.languagePacks != [ ]) (
          lib.listToAttrs (
            map (
              lang:
              lib.nameValuePair "langpack-${lang}@firefox.mozilla.org" {
                installation_mode = "normal_installed";
                install_url = "https://releases.mozilla.org/pub/firefox/releases/${cfg.package.version}/linux-x86_64/xpi/${lang}.xpi";
              }
            ) cfg.languagePacks
          )
        );
      };
    }
  );
}
