# Cargo.nix and crate-hashes.json were created with:
# - `nix run '.#crate2nix' -- generate -f ~/ref/repos/gnome/fractal/Cargo.toml`
# or, for devel crate2nix:
# - `nix shell -f https://github.com/kolloch/crate2nix/tarball/master`
#   - `crate2nix generate -f ~/ref/repos/gnome/fractal/Cargo.toml`
#
# to update:
# - `git fetch` in `~/ref/repos/gnome/fractal/`
# - re-run that crate2nix step
# - update `src` rev to match the local checkout!

{ pkgs
, lib
, stdenv
, appstream-glib
, buildPackages
, cargo
, defaultCrateOverrides
, desktop-file-utils
, fetchFromGitHub
, gdk-pixbuf
, glib
, gst_all_1
, gtk4
, gtksourceview5
, libadwaita
, libshumate
, meson
, ninja
, openssl
, pipewire
, pkg-config
, rust
, rustPlatform
, sqlite
, wrapGAppsHook4
, writeText
, xdg-desktop-portal
, optimize ? true
, crateOverrideFn ? x: x
}:
let mkConfigured = { optimize }:
let
  # `optimize` option applies only to the top-level build; not fractal's dependencies.
  # as of 2023/10/29:
  # - opt-level=0: builds in 1min, 105M binary
  # - opt-level=1: builds in 2.25hr, 75M binary
  # - opt-level=2: builds in 2.25hr
  # - opt-level=3: builds in 2.25hr, 68-70M binary
  # as of 2023/10/30:
  # - opt-level=3: builds in 5min, 71M binary
  optFlags = if optimize then "-C opt-level=3" else "-C opt-level=0";

  extraCrateOverrides = {
    fractal = attrs: attrs // {
      src = pkgs.fetchFromGitLab {
        domain = "gitlab.gnome.org";
        owner = "GNOME";
        repo = "fractal";
        # rev = "6";
        # hash = "sha256-J4Jb7G5Rfou3N7mytetIdLl0dGY5dSvTjnu8aj4kWXQ=";
        rev = "8489c25e4b2433642e63fe263fc0145a96e0b9aa";  # 6-unstable-2024-01-31; last commit before libadwaita 1.5
        hash = "sha256-SZjVQz3gHAJoxhjfq0N7UHs/0MVx8de4FVGxQ5ZJ7sg=";
      };
      codegenUnits = 256;  #< this does get plumbed, but doesn't seem to affect build speed
      outputs = [ "out" ];  # default is "out" and "lib", but that somehow causes cycles
      outputDev = [ "out" ];
      nativeBuildInputs = [
        appstream-glib  # optional, for validation
        desktop-file-utils  # for update-desktop-database
        glib  # for glib-compile-resources, gettext
        gtk4  # for gtk4-update-icon-cache
        meson
        ninja
        pkg-config
        wrapGAppsHook4
      ];
      buildInputs = [
        glib
        gst_all_1.gst-plugins-bad
        gst_all_1.gst-plugins-base
        gst_all_1.gst-plugins-good
        gst_all_1.gstreamer
        gtk4
        gtksourceview5
        libadwaita
        libshumate
        openssl
        pipewire
        sqlite
        xdg-desktop-portal
      ];

      mesonFlags = let
        # this gets meson to shutup about rustc not producing executables.
        # kinda silly though, since we patch out the actual cargo (rustc) invocations.
        crossFile = writeText "cross-file.conf" ''
        [binaries]
        rust = [ 'rustc', '--target', '${rust.toRustTargetSpec stdenv.hostPlatform}' ]
      '';
      in
        lib.optionals (stdenv.hostPlatform != stdenv.buildPlatform) [
          "--cross-file=${crossFile}"
        ];

      # patch so meson will invoke our `crate2nix_cmd.sh` instead of cargo
      postPatch = ''
        substituteInPlace src/meson.build \
          --replace-fail 'cargo_options,'  "" \
          --replace-fail "cargo, 'build',"  "'bash', 'crate2nix_cmd.sh'," \
          --replace-fail "'src' / rust_target" "'target/bin'"
      '';
      postConfigure = ''
        # copied from <pkgs/development/tools/build-managers/meson/setup-hook.sh>
        mesonFlags="--prefix=$prefix $mesonFlags"
        mesonFlags="\
            --libdir=''${!outputLib}/lib --libexecdir=''${!outputLib}/libexec \
            --bindir=''${!outputBin}/bin --sbindir=''${!outputBin}/sbin \
            --includedir=''${!outputInclude}/include \
            --mandir=''${!outputMan}/share/man --infodir=''${!outputInfo}/share/info \
            --localedir=''${!outputLib}/share/locale \
            -Dauto_features=''${mesonAutoFeatures:-enabled} \
            -Dwrap_mode=''${mesonWrapMode:-nodownload} \
            $mesonFlags"

        mesonFlags="''${crossMesonFlags+$crossMesonFlags }--buildtype=''${mesonBuildType:-plain} $mesonFlags"

        echo "meson flags: $mesonFlags ''${mesonFlagsArray[@]}"

        meson setup build $mesonFlags "''${mesonFlagsArray[@]}"
        cd build
      '';
      preBuild = ''
        build_bin() {
          # build_bin is what buildRustCrate would use to invoke rustc, but we want to drive the build
          # with meson instead. however, meson doesn't know how to plumb our rust dependencies into cargo,
          # so we still need to use build_bin for just one portion of the build.
          #
          # so, this mocks out the original build_bin:
          # - we patch upstream fractal to call our `crate2nix_cmd.sh` when it wants to compile the rust.
          # - we don't actually invoke meson (ninja) at all here, but rather in the `installPhase`.
          #   if we invoked it here, the whole build would just get re-done in installPhase anyway.
          #
          # rustc invocation copied from <pkgs/build-support/rust/build-rust-crate/lib.sh>
          crate_name_=fractal
          main_file=../src/main.rs
          fix_link="-C linker=${stdenv.cc}/bin/${stdenv.cc.targetPrefix}cc"
          EXTRA_RUSTC_FLAGS="$EXTRA_RUSTC_FLAGS ${optFlags}"
          cat >> crate2nix_cmd.sh <<EOF
            set -x
            rmdir target/bin
            rmdir target
            ln -s ../target .
            rustc \
            $fix_link \
            --crate-name $crate_name_ \
            $main_file \
            --crate-type bin \
            $BIN_RUSTC_OPTS \
            --out-dir target/bin \
            -L dependency=target/deps \
            $LINK \
            $EXTRA_LINK_ARGS \
            $EXTRA_LINK_ARGS_BINS \
            $EXTRA_LIB \
            --cap-lints allow \
            $BUILD_OUT_DIR \
            $EXTRA_BUILD \
            $EXTRA_FEATURES \
            $EXTRA_RUSTC_FLAGS \
            --color ''${colors}
      EOF
        }
      '';

      installPhase = "ninjaInstallPhase";
    };

    # TODO: upstream these into `pkgs/build-support/rust/default-crate-overrides.nix`

    clang-sys = attrs: attrs // {
      LIBCLANG_PATH = "${buildPackages.llvmPackages.libclang.lib}/lib";
    };
    gdk-pixbuf-sys = attrs: attrs // {
      nativeBuildInputs = [ pkg-config ];
      buildInputs = [ gdk-pixbuf ];
    };
    gdk4-wayland-sys = attrs: attrs // {
      nativeBuildInputs = [ pkg-config ];
      buildInputs = [ gtk4 ];  # depends on "gtk4_wayland"
    };
    gdk4-x11-sys = attrs: attrs // {
      nativeBuildInputs = [ pkg-config ];
      buildInputs = [ gtk4 ];  # depends on "gtk4_x11"
    };
    gio-sys = attrs: attrs // {
      nativeBuildInputs = [ pkg-config ];
      buildInputs = [ glib ];
    };
    gobject-sys = attrs: attrs // {
      nativeBuildInputs = [ pkg-config ];
      buildInputs = [ glib ];
    };
    gstreamer-audio-sys = attrs: attrs // {
      nativeBuildInputs = [ pkg-config ];
      buildInputs = [ gst_all_1.gst-plugins-base ];
    };
    gstreamer-base-sys = attrs: attrs // {
      nativeBuildInputs = [ pkg-config ];
      buildInputs = [ gst_all_1.gst-plugins-base ];
    };
    gstreamer-pbutils-sys = attrs: attrs // {
      nativeBuildInputs = [ pkg-config ];
      buildInputs = [ gst_all_1.gst-plugins-base ];
    };
    gstreamer-play-sys = attrs: attrs // {
      nativeBuildInputs = [ pkg-config ];
      buildInputs = [
        gst_all_1.gst-plugins-bad
        gst_all_1.gst-plugins-base
      ];
    };
    gstreamer-sys = attrs: attrs // {
      nativeBuildInputs = [ pkg-config ];
      buildInputs = [ gst_all_1.gst-plugins-base ];
    };
    gstreamer-video-sys = attrs: attrs // {
      nativeBuildInputs = [ pkg-config ];
      buildInputs = [ gst_all_1.gst-plugins-base ];
    };
    gst-plugin-gtk4 = attrs: attrs // {
      # [package.metadata.capi.pkg_config]
      # requires_private = "gstreamer-1.0, gstreamer-base-1.0, gstreamer-video-1.0, gtk4, gobject-2.0, glib-2.0, gmodule-2.0"
      nativeBuildInputs = [ pkg-config ];
      buildInputs = [
        gst_all_1.gst-plugins-base
        gst_all_1.gst-libav
        gtk4
        glib
      ];
      CARGO_PKG_REPOSITORY = "nixpkgs";
      # it wants `type = [ "cdylib" "rlib" ]` but that causes a link format failure on cross compilation
      #   (tries to link aarch64 gstgtk4.o file with the x86_64 linker).
      # default if unspecified it `type = [ "lib" ]`
      type = [ "rlib" ];
    };
    libadwaita-sys = attrs: attrs // {
      nativeBuildInputs = [ pkg-config ];
      buildInputs = [ libadwaita ];
    };
    libshumate-sys = attrs: attrs // {
      nativeBuildInputs = [ pkg-config ];
      buildInputs = [ libshumate gtk4 ];
    };
    libspa-sys = attrs: attrs // {
      nativeBuildInputs = [ pkg-config rustPlatform.bindgenHook ];
      buildInputs = [ pipewire ];

      # bindgenHook does the equivalent of this:
      # preConfigure = (attrs.preConfigure or "") + ''
      #   # export BINDGEN_EXTRA_CLANG_ARGS="$NIX_CFLAGS_COMPILE"
      #   export BINDGEN_EXTRA_CLANG_ARGS="$(< ${clang}/nix-support/cc-cflags) $(< ${clang}/nix-support/libc-cflags) $(< ${clang}/nix-support/libcxx-cxxflags) $NIX_CFLAGS_COMPILE"
      # '';
      # LIBCLANG_PATH = "${buildPackages.llvmPackages.libclang.lib}/lib";
    };
    libspa = attrs: attrs // {
      # not sure why the non-sys version of this crate needs pkg-config??
      nativeBuildInputs = [ pkg-config ];
      buildInputs = [ pipewire ];
    };
    matrix-sdk-crypto = attrs: attrs // {
      # src = fetchFromGitHub {
      #   owner = "matrix-org";
      #   repo = "matrix-rust-sdk";
      #   # rev = "matrix-sdk-crypto-0.6.0";  # 2022-09-28. "use of undeclared crate or module `event_listener`"
      #   # hash = "sha256-ozWzXrT+8U2BoJ/KDsRK+RIYpZTleXOw9U+KylRrnnE=";
      #   # rev = "ceeb5e78b6de782726c50c973a8dbbd46f9b2904";  # main
      #   # hash = "sha256-nq0pd/q8T92t7HKV+NzZwleeFWz6tAh9wuuDcf1y+Y8=";
      #   # rev = "894f4c218dba51f39c5393e624bbc763f0ff97cb"; # 2023-11-20
      #   # hash = "sha256-v1i0fm3E4a/tU+xLjs5qVVOJk696l+OMdDJ0P95XBhM=";
      #   # rev = "8895ce40d13faa79012144c97044990284215758"; # 2023-11-07; tagged by fractal
      #   rev = "4643bae28445e058080896a280083b32fd403146";  # old pin. "use of undeclared crate or module `base64`"
      #   hash = "sha256-A1oKNbEx2A6WwvYcNSW53Fd6QWwr0QFJtrsJXO2KInE=";
      # };
      # src = pkgs.fetchgit {
      #   url = "https://github.com/matrix-org/matrix-rust-sdk.git";
      #   rev = "4643bae28445e058080896a280083b32fd403146";  # old pin. "use of undeclared crate or module `base64`"
      #   hash = "sha256-A1oKNbEx2A6WwvYcNSW53Fd6QWwr0QFJtrsJXO2KInE=";
      # };

      # features = attrs.features ++ [ "ruma-common" ];
      # buildInputs = [ cargoNix.workspaceMembers.ruma-common.build ];
      # buildInputs = [
      #   (cargoNix.internal.buildRustCrateWithFeatures {
      #     packageId = "ruma-common";
      #     features = [ "default" ];
      #   })
      # ];
    };
    pipewire-sys = attrs: attrs // {
      nativeBuildInputs = [ pkg-config rustPlatform.bindgenHook ];
      buildInputs = [ pipewire ];

      # bindgenHook does the equivalent of this:
      # preConfigure = (attrs.preConfigure or "") + ''
      #   # export BINDGEN_EXTRA_CLANG_ARGS="$NIX_CFLAGS_COMPILE"
      #   export BINDGEN_EXTRA_CLANG_ARGS="$(< ${clang}/nix-support/cc-cflags) $(< ${clang}/nix-support/libc-cflags) $(< ${clang}/nix-support/libcxx-cxxflags) $NIX_CFLAGS_COMPILE"
      # '';
      # LIBCLANG_PATH = "${buildPackages.llvmPackages.libclang.lib}/lib";
    };
    ring = attrs: attrs // {
      # if after an update you see:
      # ```
      # > assertion `left == right` failed
      # >   left: "ring_core_0_17_5"
      # >  right: "ring_core_0_17_7"
      # ```
      # just update this patch to reflect the right-hand side
      # CARGO_MANIFEST_LINKS = "ring_core_0_17_7";
      postPatch = (attrs.postPatch or "") + ''
        substituteInPlace build.rs --replace-fail \
          'links = std::env::var("CARGO_MANIFEST_LINKS").unwrap();' 'links = "ring_core_0_17_7".to_string();'
      '';
    };
    sourceview5-sys = attrs: attrs // {
      nativeBuildInputs = [ pkg-config ];
      buildInputs = [ gtksourceview5 ];
    };
  };

  defaultCrateOverrides' = defaultCrateOverrides // (lib.mapAttrs (crate: fn:
    # map each `extraCrateOverrides` to first pass their attrs through `defaultCrateOverrides`
    attrs: fn ((defaultCrateOverrides."${crate}" or (a: a)) attrs)
  ) extraCrateOverrides);

  crate2NixOverrides = crates: crates // {
    # crate2nix sometimes "misses" dependencies, or gets them wrong in a way that crateOverrides can't patch.
    # this function lets me patch over Cargo.nix without actually modifying it by hand.
    matrix-sdk = crates.matrix-sdk // {
      dependencies = crates.matrix-sdk.dependencies ++ [
        {
          name = "ruma-events";
          packageId = "ruma-events";
        }
      ];
    };
    matrix-sdk-base = crates.matrix-sdk-base // {
      dependencies = crates.matrix-sdk-base.dependencies ++ [
        {
          name = "ruma-events";
          packageId = "ruma-events";
        }
      ];
    };
    matrix-sdk-crypto = crates.matrix-sdk-crypto // {
      dependencies = crates.matrix-sdk-crypto.dependencies ++ [
        {
          name = "ruma-common";
          packageId = "ruma-common";
        }
      ];
    };
  };

  cargoNix = import ./Cargo.nix {
    inherit pkgs;
    release = false;  #< XXX(2023/12/06): `release=true` is incompatible with cross compilation
    rootFeatures = [ ];  #< avoids --cfg feature="default", simplifying the rustc CLI so that i can pass it around easier
    defaultCrateOverrides = defaultCrateOverrides';
  };

  builtCrates = cargoNix.internal.builtRustCratesWithFeatures {
    packageId = "fractal";
    features = [];
    buildRustCrateForPkgsFunc = pkgs: crateArgs: let
      crateDeriv = (pkgs.buildRustCrate.override {
        defaultCrateOverrides = defaultCrateOverrides';
      }) crateArgs;
    in
      crateOverrideFn crateDeriv;
    crateConfigs = crate2NixOverrides cargoNix.internal.crates;
    runTests = false;
  };
  fractalDefault = builtCrates.crates.fractal;
in
  fractalDefault.overrideAttrs (super: {
    passthru = (super.passthru or {}) // {
      optimized = mkConfigured { optimize = true; };
      unoptimized = mkConfigured { optimize = false; };
      inherit (builtCrates) crates;
    };
  });
in
  mkConfigured { inherit optimize; }
