{ lib
, callPackage
, stdenvNoCC
, runCommand
, makeWrapper
, llvmPackages_13
, cacert
, flutter
, jq
}:

# absolutely no mac support for now

{ pubGetScript ? "flutter pub get"
, flutterBuildFlags ? [ ]
, runtimeDependencies ? [ ]
, customPackageOverrides ? { }
, autoDepsList ? false
, depsListFile ? null
, vendorHash ? ""
, pubspecLockFile ? null
, nativeBuildInputs ? [ ]
, preUnpack ? ""
, postFixup ? ""
, extraWrapProgramArgs ? ""
, ...
}@args:
let
  flutterSetupScript = ''
    # Pub needs SSL certificates. Dart normally looks in a hardcoded path.
    # https://github.com/dart-lang/sdk/blob/3.1.0/runtime/bin/security_context_linux.cc#L48
    #
    # Dart does not respect SSL_CERT_FILE...
    # https://github.com/dart-lang/sdk/issues/48506
    # ...and Flutter does not support --root-certs-file, so the path cannot be manually set.
    # https://github.com/flutter/flutter/issues/56607
    # https://github.com/flutter/flutter/issues/113594
    #
    # libredirect is of no use either, as Flutter does not pass any
    # environment variables (including LD_PRELOAD) to the Pub process.
    #
    # Instead, Flutter is patched to allow the path to the Dart binary used for
    # Pub commands to be overriden.
    export NIX_FLUTTER_PUB_DART="${runCommand "dart-with-certs" { nativeBuildInputs = [ makeWrapper ]; } ''
      mkdir -p "$out/bin"
      makeWrapper ${flutter.dart}/bin/dart "$out/bin/dart" \
        --add-flags "--root-certs-file=${cacert}/etc/ssl/certs/ca-bundle.crt"
    ''}/bin/dart"

    export HOME="$NIX_BUILD_TOP"
    flutter config --no-analytics &>/dev/null # mute first-run
    flutter config --enable-linux-desktop >/dev/null
  '';

  deps = callPackage ../dart/fetch-dart-deps { dart = flutter; } {
    sdkSetupScript = flutterSetupScript;
    inherit pubGetScript vendorHash pubspecLockFile;
    buildDrvArgs = args;
  };

  baseDerivation = llvmPackages_13.stdenv.mkDerivation (finalAttrs: args // {
    inherit flutterBuildFlags runtimeDependencies;

    outputs = [ "out" "debug" ];

    nativeBuildInputs = [
      makeWrapper
      deps
      flutter
      jq
    ] ++ nativeBuildInputs;

    preUnpack = ''
      ${lib.optionalString (!autoDepsList) ''
        if ! { [ '${lib.boolToString (depsListFile != null)}' = 'true' ] ${lib.optionalString (depsListFile != null) "&& cmp -s <(jq -Sc . '${depsListFile}') <(jq -Sc . '${finalAttrs.passthru.depsListFile}')"}; }; then
          echo 1>&2 -e '\nThe dependency list file was either not given or differs from the expected result.' \
                      '\nPlease choose one of the following solutions:' \
                      '\n - Duplicate the following file and pass it to the depsListFile argument.' \
                      '\n   ${finalAttrs.passthru.depsListFile}' \
                      '\n - Set autoDepsList to true (not supported by Hydra or permitted in Nixpkgs)'.
          exit 1
        fi
      ''}

      ${preUnpack}
    '';

    configurePhase = ''
      runHook preConfigure

      ${flutterSetupScript}

      runHook postConfigure
    '';

    buildPhase = ''
      runHook preBuild

      mkdir -p build/flutter_assets/fonts

      doPubGet flutter pub get --offline -v
      flutter build linux -v --release --split-debug-info="$debug" ${builtins.concatStringsSep " " (map (flag: "\"${flag}\"") finalAttrs.flutterBuildFlags)}

      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

      # 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
    '';

    postFixup = ''
      # Add runtime library dependencies to the LD_LIBRARY_PATH.
      # For some reason, the RUNPATH of the executable is not used to load dynamic libraries in dart:ffi with DynamicLibrary.open().
      #
      # This could alternatively be fixed with patchelf --add-needed, but this would cause all the libraries to be opened immediately,
      # which is not what application authors expect.
      for f in "$out"/bin/*; do
        wrapProgram "$f" \
          --suffix LD_LIBRARY_PATH : '${lib.makeLibraryPath finalAttrs.runtimeDependencies}' \
          ${extraWrapProgramArgs}
      done

      ${postFixup}
    '';

    passthru = (args.passthru or {}) // {
      inherit (deps) depsListFile;
    };
  });

  packageOverrideRepository = (callPackage ../../development/compilers/flutter/package-overrides { }) // customPackageOverrides;
  productPackages = builtins.filter (package: package.kind != "dev")
    (if autoDepsList
    then lib.importJSON deps.depsListFile
    else
      if depsListFile == null
      then [ ]
      else lib.importJSON depsListFile);
in
builtins.foldl'
  (prev: package:
  if packageOverrideRepository ? ${package.name}
  then
    prev.overrideAttrs
      (packageOverrideRepository.${package.name} {
        inherit (package)
          name
          version
          kind
          source
          dependencies;
      })
  else prev)
  baseDerivation
  productPackages
