# This is a terrible idea (at least from the app developer's perspective)
# because what we're doing is a massive hack. We're essentially instilling a
# forced isolated environment for the settings backend to look into. This will
# go downhill very badly once the dconf-enabled wrapper has mismatched
# configuration with the wider environment. Once that occurred, this is a
# problem neither the app developer nor the system maintainers are to blame but
# the user. So yeah, it is terrible to put this into wrapper-manager-fds
# upstream and it may be here for the rest of time.
#
# In other words, dconf is just not built for this case.
{ config, lib, pkgs, ... }:

let
  cfg = config.dconf;

  settingsFormat = {
    type = with lib.types;
      let
        valueType = (oneOf [
          bool
          float
          int
          str
          (listOf valueType)
        ]) // {
          description = "dconf value";
        };
      in
        attrsOf (attrsOf valueType);

    generate = name: value:
      pkgs.writeTextDir "/dconf/${name}" (lib.generators.toDconfINI value);
  };

  dconfModuleFactory = { isGlobal ? false }: {
    enable = lib.mkEnableOption "configuration with dconf" // lib.optionalAttrs (!isGlobal) {
      default = cfg.enable;
    };

    package = lib.mkPackageOption pkgs "dconf" { } // lib.optionalAttrs (!isGlobal) {
      default = cfg.package;
    };

    settings = lib.mkOption {
      type = settingsFormat.type;
      default = { };
      description = if isGlobal then ''
        Global settings to be applied per dconf-enabled wrapper.
      '' else ''
        The settings of the dconf database that the wrapper uses.
      '';
      example = lib.literalExpression ''
        {
          "org/gnome/nautilus/list-view".use-tree-view = true;
          "org/gnome/nautilus/preferences".show-create-link = true;
          "org/gtk/settings/file-chooser" = {
            sort-directories-first = true;
            show-hidden = true;
          };
        }
      '';
    };

    keyfiles = lib.mkOption {
      type = with lib.types; listOf path;
      description = if isGlobal then ''
        Global list of keyfiles to be included to each dconf-enabled wrapper.
      '' else ''
        Additional list of keyfiles to be included as part of the dconf
        database.
      '';
      default = if isGlobal then [ ] else [ "user-db" ];
      example = lib.literalExpression ''
        [
          ./config/dconf/90-extra-settings.conf
        ]
      '';
    };

    profile = lib.mkOption {
      type = with lib.types; listOf str;
      description = if isGlobal then ''
        Global list of dconf database that will be used for each dconf-enabled
        wrappers.
      '' else ''
        A list of dconf databases that will be used for the main dconf
        profile of the dconf-configured wrapper.
      '';
      default = [ "user-db:user" ];
      defaultText = ''
        "user-db:user" as the writeable database alongside the generated
        database file from our settings.
      '';
    };
  };
in
{
  options.dconf = dconfModuleFactory { isGlobal = true; };

  options.wrappers =
    let
      dconfSubmodule = { config, lib, name, ... }: let
        submoduleCfg = config.dconf;

        dconfProfileFile =
          pkgs.writeText
            "dconf-profile"
            (lib.concatMapStrings (db: "${db}\n") submoduleCfg.profile);

        dconfSettings =
          settingsFormat.generate "wrapper-manager-dconf-${config.executableName}-settings" submoduleCfg.settings;

        keyfilesDir = pkgs.symlinkJoin {
          name = "wrapper-manager-dconf-${config.executableName}";
          paths = submoduleCfg.keyfiles ++ [ "${dconfSettings}/dconf" ];
        };

        dconfSettingsDatabase =
          pkgs.runCommand "wrapper-manager-dconf-${config.executableName}-database" {
            nativeBuildInputs = [ submoduleCfg.package ];
          } ''
            dconf compile ${builtins.placeholder "out"} "${keyfilesDir}"
          '';
      in {
        options.dconf = dconfModuleFactory { isGlobal = false; } // {
          databaseDrv = lib.mkOption {
            type = lib.types.package;
            description = ''
              Derivation containing the compiled dconf database. Useful for
              integrating with your own module.
            '';
            readOnly = true;
          };
        };

        config = lib.mkIf submoduleCfg.enable {
          env.DCONF_PROFILE.value = dconfProfileFile;

          dconf = {
            profile = lib.mkMerge [
              cfg.profile

              (lib.mkAfter [ (builtins.toString submoduleCfg.databaseDrv) ])
            ];
            keyfiles = cfg.keyfiles;
            settings = cfg.settings;
            databaseDrv = dconfSettingsDatabase;
          };
        };
      };
    in
      lib.mkOption {
        type = with lib.types; attrsOf (submodule dconfSubmodule);
      };
}