{ flutter
, lib
, llvmPackages_13
, cmake
, ninja
, pkg-config
, wrapGAppsHook
, autoPatchelfHook
, util-linux
, libselinux
, libsepol
, libthai
, libdatrie
, libxkbcommon
, at-spi2-core
, libsecret
, jsoncpp
, xorg
, dbus
, gtk3
, glib
, pcre
, libepoxy
, stdenvNoCC
, cacert
, git
, dart
, nukeReferences
, targetPlatform
, bash
, curl
, unzip
, which
, xz
}:

# absolutely no mac support for now

args:
let
  pl = n: "##FLUTTER_${n}_PLACEHOLDER_MARKER##";
  placeholder_deps = pl "DEPS";
  placeholder_flutter = pl "FLUTTER";
  fetchAttrs = [ "src" "sourceRoot" "setSourceRoot" "unpackPhase" "patches" ];
  getAttrsOrNull = names: attrs: lib.genAttrs names (name: if attrs ? ${name} then attrs.${name} else null);
  flutterDeps = [
    # flutter deps
    flutter.unwrapped
    bash
    curl
    flutter.dart
    git
    unzip
    which
    xz
  ];
  self =
(self: llvmPackages_13.stdenv.mkDerivation (args // {
  deps = stdenvNoCC.mkDerivation (lib.recursiveUpdate (getAttrsOrNull fetchAttrs args) {
    name = "${self.name}-deps-flutter-v${flutter.unwrapped.version}-${targetPlatform.system}.tar.gz";

    nativeBuildInputs = flutterDeps ++ [
      nukeReferences
    ];

    # avoid pub phase
    dontBuild = true;

    installPhase = ''
      . ${../fetchgit/deterministic-git}

      TMP=$(mktemp -d)

      export HOME="$TMP"
      export PUB_CACHE=''${PUB_CACHE:-"$HOME/.pub-cache"}
      export ANDROID_EMULATOR_USE_SYSTEM_LIBS=1

      flutter config --no-analytics &>/dev/null # mute first-run
      flutter config --enable-linux-desktop
      flutter packages get
      flutter build linux || true # so it downloads tools
      ${lib.optionalString (args ? flutterExtraFetchCommands) args.flutterExtraFetchCommands}

      RES="$TMP"

      mkdir -p "$RES/f"

      # so we can use lock, diff yaml
      cp "pubspec.yaml" "$RES"
      cp "pubspec.lock" "$RES"
      mv .dart_tool .flutter-plugins .flutter-plugins-dependencies .packages "$RES/f"

      # replace paths with placeholders
      find "$RES" -type f -exec sed -i \
        -e s,$TMP,${placeholder_deps},g \
        -e s,${flutter.unwrapped},${placeholder_flutter},g \
        {} +

      remove_line_matching() {
        replace_line_matching "$1" "$2" ""
      }

      replace_line_matching() {
        sed "s|.*$2.*|$3|g" -r -i "$1"
      }

      # nuke nondeterminism

      # clientId is random
      remove_line_matching "$RES/.flutter" clientId

      # deterministic git repos
      find "$RES" -iname .git -type d | while read -r repoGit; do
        make_deterministic_repo "$(dirname "$repoGit")"
      done

      # dart _fetchedAt, etc
      DART_DATE=$(date --date="@$SOURCE_DATE_EPOCH" -In | sed "s|,|.|g" | sed "s|+.*||g")
      find "$RES/.pub-cache" -iname "*.json" -exec sed -r 's|.*_fetchedAt.*|    "_fetchedAt": "'"$DART_DATE"'",|g' -i {} +
      replace_line_matching "$RES/f/.dart_tool/package_config.json" '"generated"' '"generated": "'"$DART_DATE"'",'
      replace_line_matching "$RES/f/.flutter-plugins-dependencies" '"date_created"' '"date_created": "'"$DART_DATE"'",'
      remove_line_matching "$RES/f/.packages" "Generated by pub"

      # nuke refs
      find "$RES" -type f -exec nuke-refs {} +

      # Build a reproducible tar, per instructions at https://reproducible-builds.org/docs/archives/
      tar --owner=0 --group=0 --numeric-owner --format=gnu \
          --sort=name --mtime="@$SOURCE_DATE_EPOCH" \
          -czf "$out" -C "$RES" .
    '';

    GIT_SSL_CAINFO = "${cacert}/etc/ssl/certs/ca-bundle.crt";
    SSL_CERT_FILE = "${cacert}/etc/ssl/certs/ca-bundle.crt";

    impureEnvVars = lib.fetchers.proxyImpureEnvVars ++ [
      "GIT_PROXY_COMMAND" "NIX_GIT_SSL_CAINFO" "SOCKS_SERVER"
    ];

    # unnecesarry
    dontFixup = true;

    outputHashAlgo = if self ? vendorHash then null else "sha256";
    # outputHashMode = "recursive";
    outputHash = if self ? vendorHash then
      self.vendorHash
    else if self ? vendorSha256 then
      self.vendorSha256
    else
      lib.fakeSha256;

  });

  nativeBuildInputs = flutterDeps ++ [
    # flutter dev tools
    cmake
    ninja
    pkg-config
    wrapGAppsHook
    # flutter likes dynamic linking
    autoPatchelfHook
  ] ++ lib.optionals (args ? nativeBuildInputs) args.nativeBuildInputs;

  buildInputs = [
    # cmake deps
    gtk3
    glib
    pcre
    util-linux
    # also required by cmake, not sure if really needed or dep of all packages
    libselinux
    libsepol
    libthai
    libdatrie
    xorg.libXdmcp
    xorg.libXtst
    libxkbcommon
    dbus
    at-spi2-core
    libsecret
    jsoncpp
    # build deps
    xorg.libX11
    # directly required by build
    libepoxy
  ] ++ lib.optionals (args ? buildInputs) args.buildInputs;

  # TODO: do we need this?
  NIX_LDFLAGS = "-rpath ${lib.makeLibraryPath self.buildInputs}";
  NIX_CFLAGS_COMPILE = "-I${xorg.libX11}/include";
  LD_LIBRARY_PATH = lib.makeLibraryPath self.buildInputs;

  configurePhase = ''
    runHook preConfigure

    # for some reason fluffychat build breaks without this - seems file gets overriden by some tool
    cp pubspec.yaml pubspec-backup

    # we get this from $depsFolder so disabled for now, but we might need it again once deps are fetched properly
    # flutter config --no-analytics >/dev/null 2>/dev/null # mute first-run
    # flutter config --enable-linux-desktop

    # extract deps
    depsFolder=$(mktemp -d)
    tar xzf "$deps" -C "$depsFolder"

    # after extracting update paths to point to real paths
    find "$depsFolder" -type f -exec sed -i \
      -e s,${placeholder_deps},$depsFolder,g \
      -e s,${placeholder_flutter},${flutter.unwrapped},g \
      {} +

    # ensure we're using a lockfile for the right package version
    if [ -e pubspec.lock ]; then
      # FIXME: currently this is broken. in theory this should not break, but flutter has it's own way of doing things.
      # diff -u pubspec.lock $depsFolder/pubspec.lock
      true
    else
      cp -v "$depsFolder/pubspec.lock" .
    fi
    diff -u pubspec.yaml $depsFolder/pubspec.yaml

    mv -v $(find $depsFolder/f -type f) .

    # prepare
    export HOME=$depsFolder
    export PUB_CACHE=''${PUB_CACHE:-"$HOME/.pub-cache"}
    export ANDROID_EMULATOR_USE_SYSTEM_LIBS=1

    # binaries need to be patched
    autoPatchelf -- "$depsFolder"

    runHook postConfigure
  '';

  buildPhase = ''
    runHook preBuild

    # for some reason fluffychat build breaks without this - seems file gets overriden by some tool
    mv pubspec-backup pubspec.yaml
    mkdir -p build/flutter_assets/fonts

    flutter packages get --offline -v
    flutter build linux --release -v

    runHook postBuild
  '';

  installPhase = ''
    runHook preInstall

    built=build/linux/*/release/bundle

    mkdir -p $out/bin
    mv $built $out/app

    for f in $(find $out/app -iname "*.desktop" -type f); do
      install -D $f $out/share/applications/$(basename $f)
    done

    for f in $(find $out/app -maxdepth 1 -type f); do
      ln -s $f $out/bin/$(basename $f)
    done

    # this confuses autopatchelf hook otherwise
    rm -rf "$depsFolder"

    # make *.so executable
    find $out/app -iname "*.so" -type f -exec chmod +x {} +

    # remove stuff like /build/source/packages/ubuntu_desktop_installer/linux/flutter/ephemeral
    for f in $(find $out/app -executable -type f); do
      if patchelf --print-rpath "$f" | grep /build; then # this ignores static libs (e,g. libapp.so) also
        echo "strip RPath of $f"
        newrp=$(patchelf --print-rpath $f | sed -r "s|/build.*ephemeral:||g" | sed -r "s|/build.*profile:||g")
        patchelf --set-rpath "$newrp" "$f"
      fi
    done

    runHook postInstall
  '';
})) self;
in
  self
