{ stdenv
, lib
, makeSetupHook
, makeWrapper
, gobject-introspection
, isGraphical ? true
, gtk3
, librsvg
, dconf
, callPackage
, wrapGAppsHook
, targetPackages
}:

makeSetupHook {
  name = "wrap-gapps-hook";
  propagatedBuildInputs = [
    # We use the wrapProgram function.
    makeWrapper
  ] ++ lib.optionals isGraphical [
    # TODO: remove this, packages should depend on GTK explicitly.
    gtk3

    librsvg
  ];

  # depsTargetTargetPropagated will essentially be buildInputs when wrapGAppsHook is placed into nativeBuildInputs
  # the librsvg and gtk3 above should be removed but kept to not break anything that implicitly depended on its binaries
  depsTargetTargetPropagated = assert (lib.assertMsg (!targetPackages ? raw) "wrapGAppsHook must be in nativeBuildInputs"); lib.optionals isGraphical [
    # librsvg provides a module for gdk-pixbuf to allow rendering
    # SVG icons. Most icon themes are SVG-based and so are some
    # graphics in GTK (e.g. cross for closing window in window title bar)
    # so it is pretty much required for applications using GTK.
    librsvg

    # TODO: remove this, packages should depend on GTK explicitly.
    gtk3
  ] ++ lib.optionals (!stdenv.isDarwin) [
    # It is highly probable that a program will use GSettings,
    # at minimum through GTK file chooser dialogue.
    # Let’s add a GIO module for “dconf” GSettings backend
    # to avoid falling back to “memory” backend. This is
    # required for GSettings-based settings to be persisted.
    # Unfortunately, it also requires the user to have dconf
    # D-Bus service enabled globally (e.g. through a NixOS module).
    dconf.lib
  ];
  passthru = {
    tests = let
      sample-project = ./tests/sample-project;

      testLib = callPackage ./tests/lib.nix { };
      inherit (testLib) expectSomeLineContainingYInFileXToMentionZ;
    in rec {
      # Simple derivation containing a program and a daemon.
      basic = stdenv.mkDerivation {
        name = "basic";

        src = sample-project;

        strictDeps = true;
        nativeBuildInputs = [ wrapGAppsHook ];

        installFlags = [ "bin-foo" "libexec-bar" ];
      };

      # The wrapper for executable files should add path to dconf GIO module.
      basic-contains-dconf = let
        tested = basic;
      in testLib.runTest "basic-contains-dconf" (
        testLib.skip stdenv.isDarwin ''
          ${expectSomeLineContainingYInFileXToMentionZ "${tested}/bin/foo" "GIO_EXTRA_MODULES" "${dconf.lib}/lib/gio/modules"}
          ${expectSomeLineContainingYInFileXToMentionZ "${tested}/libexec/bar" "GIO_EXTRA_MODULES" "${dconf.lib}/lib/gio/modules"}
        ''
      );

      basic-contains-gdk-pixbuf = let
        tested = basic;
      in testLib.runTest "basic-contains-gdk-pixbuf" (
        testLib.skip stdenv.isDarwin ''
          ${expectSomeLineContainingYInFileXToMentionZ "${tested}/bin/foo" "GDK_PIXBUF_MODULE_FILE" "${lib.getLib librsvg}/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache"}
          ${expectSomeLineContainingYInFileXToMentionZ "${tested}/libexec/bar" "GDK_PIXBUF_MODULE_FILE" "${lib.getLib librsvg}/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache"}
        ''
      );

      # Simple derivation containing a gobject-introspection typelib.
      typelib-Mahjong = stdenv.mkDerivation {
        name = "typelib-Mahjong";

        src = sample-project;

        strictDeps = true;

        installFlags = [ "typelib-Mahjong" ];
      };

      # Simple derivation using a typelib.
      typelib-user = stdenv.mkDerivation {
        name = "typelib-user";

        src = sample-project;

        strictDeps = true;
        nativeBuildInputs = [
          gobject-introspection
          wrapGAppsHook
        ];

        buildInputs = [
          typelib-Mahjong
        ];

        installFlags = [ "bin-foo" "libexec-bar" ];
      };

      # Testing cooperation with gobject-introspection setup hook,
      # which should populate GI_TYPELIB_PATH variable with paths
      # to typelibs among the derivation’s dependencies.
      # The resulting GI_TYPELIB_PATH should be picked up by the wrapper.
      typelib-user-has-gi-typelib-path = let
        tested = typelib-user;
      in testLib.runTest "typelib-user-has-gi-typelib-path" ''
        ${expectSomeLineContainingYInFileXToMentionZ "${tested}/bin/foo" "GI_TYPELIB_PATH" "${typelib-Mahjong}/lib/girepository-1.0"}
        ${expectSomeLineContainingYInFileXToMentionZ "${tested}/libexec/bar" "GI_TYPELIB_PATH" "${typelib-Mahjong}/lib/girepository-1.0"}
      '';

      # Simple derivation containing a gobject-introspection typelib in lib output.
      typelib-Bechamel = stdenv.mkDerivation {
        name = "typelib-Bechamel";

        outputs = [ "out" "lib" ];

        src = sample-project;

        strictDeps = true;

        makeFlags = [
          "LIBDIR=${placeholder "lib"}/lib"
        ];

        installFlags = [ "typelib-Bechamel" ];
      };

      # Simple derivation using a typelib from non-default output.
      typelib-multiout-user = stdenv.mkDerivation {
        name = "typelib-multiout-user";

        src = sample-project;

        strictDeps = true;
        nativeBuildInputs = [
          gobject-introspection
          wrapGAppsHook
        ];

        buildInputs = [
          typelib-Bechamel
        ];

        installFlags = [ "bin-foo" "libexec-bar" ];
      };

      # Testing cooperation with gobject-introspection setup hook,
      # which should populate GI_TYPELIB_PATH variable with paths
      # to typelibs among the derivation’s dependencies,
      # even when they are not in default output.
      # The resulting GI_TYPELIB_PATH should be picked up by the wrapper.
      typelib-multiout-user-has-gi-typelib-path = let
        tested = typelib-multiout-user;
      in testLib.runTest "typelib-multiout-user-has-gi-typelib-path" ''
        ${expectSomeLineContainingYInFileXToMentionZ "${tested}/bin/foo" "GI_TYPELIB_PATH" "${typelib-Bechamel.lib}/lib/girepository-1.0"}
        ${expectSomeLineContainingYInFileXToMentionZ "${tested}/libexec/bar" "GI_TYPELIB_PATH" "${typelib-Bechamel.lib}/lib/girepository-1.0"}
      '';

      # Simple derivation that contains a typelib as well as a program using it.
      typelib-self-user = stdenv.mkDerivation {
        name = "typelib-self-user";

        src = sample-project;

        strictDeps = true;
        nativeBuildInputs = [
          gobject-introspection
          wrapGAppsHook
        ];

        installFlags = [ "typelib-Cow" "bin-foo" "libexec-bar" ];
      };

      # Testing cooperation with gobject-introspection setup hook,
      # which should add the path to derivation’s own typelibs
      # to GI_TYPELIB_PATH variable.
      # The resulting GI_TYPELIB_PATH should be picked up by the wrapper.
      # https://github.com/NixOS/nixpkgs/issues/85515
      typelib-self-user-has-gi-typelib-path = let
        tested = typelib-self-user;
      in testLib.runTest "typelib-self-user-has-gi-typelib-path" ''
        ${expectSomeLineContainingYInFileXToMentionZ "${tested}/bin/foo" "GI_TYPELIB_PATH" "${typelib-self-user}/lib/girepository-1.0"}
        ${expectSomeLineContainingYInFileXToMentionZ "${tested}/libexec/bar" "GI_TYPELIB_PATH" "${typelib-self-user}/lib/girepository-1.0"}
      '';
    };
  };
} ./wrap-gapps-hook.sh
