# shellcheck shell=bash

_dotnetIsSolution() {
  dotnet sln ${1:+"$1"} list 2>/dev/null
}

dotnetConfigurePhase() {
  echo "Executing dotnetConfigureHook"

  runHook preConfigure

  local -a projectFiles flags runtimeIds
  concatTo projectFiles dotnetProjectFiles dotnetTestProjectFiles
  concatTo flags dotnetFlags dotnetRestoreFlags
  concatTo runtimeIds dotnetRuntimeIds

  if [[ -z ${enableParallelBuilding-} ]]; then
    flags+=(--disable-parallel)
  fi

  if [[ -v dotnetSelfContainedBuild ]]; then
    if [[ -n $dotnetSelfContainedBuild ]]; then
      flags+=("-p:SelfContained=true")
    else
      flags+=("-p:SelfContained=false")
    fi
  fi

  dotnetRestore() {
    local -r projectFile="${1-}"
    for runtimeId in "${runtimeIds[@]}"; do
      dotnet restore ${1+"$projectFile"} \
             -p:ContinuousIntegrationBuild=true \
             -p:Deterministic=true \
             -p:NuGetAudit=false \
             --runtime "$runtimeId" \
             "${flags[@]}"
    done
  }

  if [[ -f .config/dotnet-tools.json || -f dotnet-tools.json ]]; then
    dotnet tool restore
  fi

  # dotnetGlobalTool is set in buildDotnetGlobalTool to patch dependencies but
  # avoid other project-specific logic. This is a hack, but the old behavior
  # is worse as it relied on a bug: setting projectFile to an empty string
  # made the hooks actually skip all project-specific logic. It’s hard to keep
  # backwards compatibility with this odd behavior now since we are using
  # arrays, so instead we just pass a variable to indicate that we don’t have
  # projects.
  if [[ -z ${dotnetGlobalTool-} ]]; then
    if (( ${#projectFiles[@]} == 0 )); then
      dotnetRestore
    fi

    local projectFile
    for projectFile in "${projectFiles[@]}"; do
      dotnetRestore "$projectFile"
    done
  fi

  runHook postConfigure

  echo "Finished dotnetConfigureHook"
}

if [[ -z "${dontDotnetConfigure-}" && -z "${configurePhase-}" ]]; then
  configurePhase=dotnetConfigurePhase
fi

dotnetBuildPhase() {
  echo "Executing dotnetBuildHook"

  runHook preBuild

  local -r dotnetBuildType="${dotnetBuildType-Release}"

  local -a projectFiles flags runtimeIds
  concatTo projectFiles dotnetProjectFiles dotnetTestProjectFiles
  concatTo flags dotnetFlags dotnetBuildFlags
  concatTo runtimeIds dotnetRuntimeIds

  if [[ -n "${enableParallelBuilding-}" ]]; then
    local -r maxCpuFlag="$NIX_BUILD_CORES"
    local -r parallelBuildFlag="true"
  else
    local -r maxCpuFlag="1"
    local -r parallelBuildFlag="false"
  fi

  if [[ -v dotnetSelfContainedBuild ]]; then
    if [[ -n $dotnetSelfContainedBuild ]]; then
      flags+=("-p:SelfContained=true")
    else
      flags+=("-p:SelfContained=false")
    fi
  fi

  if [[ -n ${dotnetUseAppHost-} ]]; then
    flags+=("-p:UseAppHost=true")
  fi

  if [[ -n ${version-} ]]; then
    flags+=("-p:InformationalVersion=$version")
  fi

  if [[ -n ${versionForDotnet-} ]]; then
    flags+=("-p:Version=$versionForDotnet")
  fi

  dotnetBuild() {
    local -r projectFile="${1-}"

    local useRuntime=
    _dotnetIsSolution "$projectFile" || useRuntime=1

    for runtimeId in "${runtimeIds[@]}"; do
      local runtimeIdFlags=()
      if [[ -n $useRuntime ]]; then
        runtimeIdFlags+=("--runtime" "$runtimeId")
      fi

      dotnet build ${1+"$projectFile"} \
             -maxcpucount:"$maxCpuFlag" \
             -p:BuildInParallel="$parallelBuildFlag" \
             -p:ContinuousIntegrationBuild=true \
             -p:Deterministic=true \
             -p:OverwriteReadOnlyFiles=true \
             --configuration "$dotnetBuildType" \
             --no-restore \
             "${runtimeIdFlags[@]}" \
             "${flags[@]}"
    done
  }

  if (( ${#projectFiles[@]} == 0 )); then
    dotnetBuild
  fi

  local projectFile
  for projectFile in "${projectFiles[@]}"; do
    dotnetBuild "$projectFile"
  done

  runHook postBuild

  echo "Finished dotnetBuildHook"
}

if [[ -z ${dontDotnetBuild-} && -z ${buildPhase-} ]]; then
  buildPhase=dotnetBuildPhase
fi

dotnetCheckPhase() {
  echo "Executing dotnetCheckHook"

  runHook preCheck

  local -r dotnetBuildType="${dotnetBuildType-Release}"

  local -a projectFiles testProjectFiles testFilters disabledTests flags runtimeIds runtimeDeps
  concatTo projectFiles dotnetProjectFiles
  concatTo testProjectFiles dotnetTestProjectFiles
  concatTo testFilters dotnetTestFilters
  concatTo disabledTests dotnetDisabledTests
  concatTo flags dotnetFlags dotnetTestFlags
  concatTo runtimeIds dotnetRuntimeIds
  concatTo runtimeDeps dotnetRuntimeDeps

  if (( ${#disabledTests[@]} > 0 )); then
    local disabledTestsFilters=("${disabledTests[@]/#/FullyQualifiedName!=}")
    testFilters=( "${testFilters[@]}" "${disabledTestsFilters[@]//,/%2C}" )
  fi

  if (( ${#testFilters[@]} > 0 )); then
    local OLDIFS="$IFS" IFS='&'
    flags+=("--filter:${testFilters[*]}")
    IFS="$OLDIFS"
  fi

  local libraryPath="${LD_LIBRARY_PATH-}"
  if (( ${#runtimeDeps[@]} > 0 )); then
    local libraryPaths=("${runtimeDeps[@]/%//lib}")
    local OLDIFS="$IFS" IFS=':'
    libraryPath="${libraryPaths[*]}${libraryPath:+':'}$libraryPath"
    IFS="$OLDIFS"
  fi

  if [[ -n ${enableParallelBuilding-} ]]; then
    local -r maxCpuFlag="$NIX_BUILD_CORES"
  else
    local -r maxCpuFlag="1"
  fi

  local projectFile runtimeId
  for projectFile in "${testProjectFiles[@]-${projectFiles[@]}}"; do
    local useRuntime=
    _dotnetIsSolution "$projectFile" || useRuntime=1

    for runtimeId in "${runtimeIds[@]}"; do
      local runtimeIdFlags=()
      if [[ -n $useRuntime ]]; then
        runtimeIdFlags=("--runtime" "$runtimeId")
      fi

      LD_LIBRARY_PATH=$libraryPath \
        dotnet test "$projectFile" \
        -maxcpucount:"$maxCpuFlag" \
        -p:ContinuousIntegrationBuild=true \
        -p:Deterministic=true \
        --configuration "$dotnetBuildType" \
        --no-restore \
        --no-build \
        --logger "console;verbosity=normal" \
        "${runtimeIdFlags[@]}" \
        "${flags[@]}"
    done
  done

  runHook postCheck

  echo "Finished dotnetCheckHook"
}

if [[ -z "${dontDotnetCheck-}" && -z "${checkPhase-}" ]]; then
  checkPhase=dotnetCheckPhase
fi

# For compatibility, convert makeWrapperArgs to an array unless we are using
# structured attributes. That is, we ensure that makeWrapperArgs is always an
# array.
# See https://github.com/NixOS/nixpkgs/blob/858f4db3048c5be3527e183470e93c1a72c5727c/pkgs/build-support/dotnet/build-dotnet-module/hooks/dotnet-fixup-hook.sh#L1-L3
# and https://github.com/NixOS/nixpkgs/pull/313005#issuecomment-2175482920
# shellcheck disable=2206
if [[ -z $__structuredAttrs ]]; then
  makeWrapperArgs=( ${makeWrapperArgs-} )
fi

# First argument is the executable you want to wrap,
# the second is the destination for the wrapper.
wrapDotnetProgram() {
  local -r dotnetRuntime=@dotnetRuntime@
  local -r wrapperPath=@wrapperPath@

  # shellcheck disable=2016
  local -r dotnetFromEnvScript='dotnetFromEnv() {
    local dotnetPath
    if command -v dotnet 2>&1 >/dev/null; then
        dotnetPath=$(which dotnet) && \
            dotnetPath=$(realpath "$dotnetPath") && \
            dotnetPath=$(dirname "$dotnetPath") && \
            export DOTNET_ROOT="$dotnetPath"
    fi
}
dotnetFromEnv'

  # shellcheck disable=2206
  local -a runtimeDeps
  concatTo runtimeDeps dotnetRuntimeDeps

  local wrapperFlags=()
  if (( ${#runtimeDeps[@]} > 0 )); then
    local libraryPath=("${runtimeDeps[@]/%//lib}")
    local OLDIFS="$IFS" IFS=':'
    wrapperFlags+=("--suffix" "LD_LIBRARY_PATH" ":" "${libraryPath[*]}")
    IFS="$OLDIFS"
  fi

  if [[ -z ${dotnetSelfContainedBuild-} ]]; then
    if [[ -n ${useDotnetFromEnv-} ]]; then
      # if dotnet CLI is available, set DOTNET_ROOT based on it. Otherwise set to default .NET runtime
      wrapperFlags+=("--suffix" "PATH" ":" "$wrapperPath")
      wrapperFlags+=("--run" "$dotnetFromEnvScript")
      if [[ -n $dotnetRuntime ]]; then
        wrapperFlags+=("--set-default" "DOTNET_ROOT" "$dotnetRuntime/share/dotnet")
        wrapperFlags+=("--suffix" "PATH" ":" "$dotnetRuntime/bin")
      fi
    elif [[ -n $dotnetRuntime ]]; then
      wrapperFlags+=("--set" "DOTNET_ROOT" "$dotnetRuntime/share/dotnet")
      wrapperFlags+=("--prefix" "PATH" ":" "$dotnetRuntime/bin")
    fi
  fi

  # shellcheck disable=2154
  makeWrapper "$1" "$2" \
              "${wrapperFlags[@]}" \
              "${gappsWrapperArgs[@]}" \
              "${makeWrapperArgs[@]}"

  echo "installed wrapper to $2"
}

dotnetFixupPhase() {
  local -r dotnetInstallPath="${dotnetInstallPath-$out/lib/$pname}"

  local executable executableBasename

  # check if dotnetExecutables is declared (including empty values, in which case we generate no executables)
  # shellcheck disable=2154
  if declare -p dotnetExecutables &>/dev/null; then
    # shellcheck disable=2206
    local -a executables
    concatTo executables dotnetExecutables
    for executable in "${executables[@]}"; do
      executableBasename=$(basename "$executable")

      local path="$dotnetInstallPath/$executable"

      if test -x "$path"; then
        wrapDotnetProgram "$path" "$out/bin/$executableBasename"
      else
        echo "Specified binary \"$executable\" is either not an executable or does not exist!"
        echo "Looked in $path"
        exit 1
      fi
    done
  else
    while IFS= read -r -d '' executable; do
      executableBasename=$(basename "$executable")
      wrapDotnetProgram "$executable" "$out/bin/$executableBasename" \;
    done < <(find "$dotnetInstallPath" ! -name "*.dll" -executable -type f -print0)
  fi
}

if [[ -z "${dontFixup-}" && -z "${dontDotnetFixup-}" ]]; then
  appendToVar preFixupPhases dotnetFixupPhase
fi

dotnetInstallPhase() {
  echo "Executing dotnetInstallHook"

  runHook preInstall

  local -r dotnetInstallPath="${dotnetInstallPath-$out/lib/$pname}"
  local -r dotnetBuildType="${dotnetBuildType-Release}"

  local -a projectFiles flags installFlags packFlags runtimeIds
  concatTo projectFiles dotnetProjectFiles
  concatTo flags dotnetFlags
  concatTo installFlags dotnetInstallFlags
  concatTo packFlags dotnetPackFlags
  concatTo runtimeIds dotnetRuntimeIds

  if [[ -v dotnetSelfContainedBuild ]]; then
    if [[ -n $dotnetSelfContainedBuild ]]; then
      installFlags+=("--self-contained")
    else
      installFlags+=("--no-self-contained")
      # https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/trim-self-contained
      # Trimming is only available for self-contained build, so force disable it here
      installFlags+=("-p:PublishTrimmed=false")
    fi
  fi

  if [[ -n ${dotnetUseAppHost-} ]]; then
    installFlags+=("-p:UseAppHost=true")
  fi

  if [[ -n ${enableParallelBuilding-} ]]; then
    local -r maxCpuFlag="$NIX_BUILD_CORES"
  else
    local -r maxCpuFlag="1"
  fi

  dotnetPublish() {
    local -r projectFile="${1-}"

    local useRuntime=
    _dotnetIsSolution "$projectFile" || useRuntime=1

    for runtimeId in "${runtimeIds[@]}"; do
      local runtimeIdFlags=()
      if [[ -n $useRuntime ]]; then
        runtimeIdFlags+=("--runtime" "$runtimeId")
      fi

      dotnet publish ${1+"$projectFile"} \
             -maxcpucount:"$maxCpuFlag" \
             -p:ContinuousIntegrationBuild=true \
             -p:Deterministic=true \
             -p:OverwriteReadOnlyFiles=true \
             --output "$dotnetInstallPath" \
             --configuration "$dotnetBuildType" \
             --no-restore \
             --no-build \
             "${runtimeIdFlags[@]}" \
             "${flags[@]}" \
             "${installFlags[@]}"
    done
  }

  dotnetPack() {
    local -r projectFile="${1-}"

    local useRuntime=
    _dotnetIsSolution "$projectFile" || useRuntime=1

    for runtimeId in "${runtimeIds[@]}"; do
      local runtimeIdFlags=()
      if [[ -n $useRuntime ]]; then
        runtimeIdFlags+=("--runtime" "$runtimeId")
        # set RuntimeIdentifier because --runtime is broken:
        # https://github.com/dotnet/sdk/issues/13983
        runtimeIdFlags+=(-p:RuntimeIdentifier="$runtimeId")
      fi

      dotnet pack ${1+"$projectFile"} \
             -maxcpucount:"$maxCpuFlag" \
             -p:ContinuousIntegrationBuild=true \
             -p:Deterministic=true \
             -p:OverwriteReadOnlyFiles=true \
             --output "$out/share/nuget/source" \
             --configuration "$dotnetBuildType" \
             --no-restore \
             --no-build \
             "${runtimeIdFlags[@]}" \
             "${flags[@]}" \
             "${packFlags[@]}"
    done
  }

  if [[ -z "${dontPublish-}" ]]; then
    if (( ${#projectFiles[@]} == 0 )); then
      dotnetPublish
    else
      local projectFile
      for projectFile in "${projectFiles[@]}"; do
        dotnetPublish "$projectFile"
      done
    fi
  fi

  if [[ -n ${packNupkg-} ]]; then
    if (( ${#projectFiles[@]} == 0 )); then
      dotnetPack
    else
      local projectFile
      for projectFile in "${projectFiles[@]}"; do
        dotnetPack "$projectFile"
      done
    fi
  fi

  runHook postInstall

  echo "Finished dotnetInstallHook"
}

if [[ -z "${dontDotnetInstall-}" && -z "${installPhase-}" ]]; then
  installPhase=dotnetInstallPhase
fi
