#!/bin/bash
# Upload Debug Symbols to Sentry Xcode Build Phase
# PWD=ios

# print commands before executing them
set -x

# REACT_NATIVE_PATH first used in RN 0.74.0 Template https://github.com/facebook/react-native/commit/289e78388a87408e215a25108cb02511a05f5c80
LOCAL_REACT_NATIVE_PATH="${REACT_NATIVE_PATH:-"../node_modules/react-native"}"

[ -z "$WITH_ENVIRONMENT" ] && WITH_ENVIRONMENT="${LOCAL_REACT_NATIVE_PATH}/scripts/xcode/with-environment.sh"

if [ -f "$WITH_ENVIRONMENT" ]; then
  # load envs if loader file exists (since rn 0.68)
  . "$WITH_ENVIRONMENT"
fi

# stop on first error (we can't use -e before as any failed command in WITH_ENVIRONMENT would stop the debug files upload)
set -e

LOCAL_NODE_BINARY=${NODE_BINARY:-node}

# The project root by default is one level up from the ios directory
# SENTRY_PROJECT_ROOT can be set to override this for monorepo setups
# where the ios directory is not directly under the project root.
# See: https://github.com/getsentry/sentry-react-native/issues/2880
RN_PROJECT_ROOT="${SENTRY_PROJECT_ROOT:-${PROJECT_DIR}/..}"

[ -z "$SENTRY_PROPERTIES" ] && export SENTRY_PROPERTIES=sentry.properties
[ -z "$SENTRY_DOTENV_PATH" ] && [ -f "$RN_PROJECT_ROOT/.env.sentry-build-plugin" ] && export SENTRY_DOTENV_PATH="$RN_PROJECT_ROOT/.env.sentry-build-plugin"

[ -z "$SOURCEMAP_FILE" ] && export SOURCEMAP_FILE="$DERIVED_FILE_DIR/main.jsbundle.map"

if [ -z "$SENTRY_CLI_EXECUTABLE" ]; then
  # Resolve from @sentry/react-native so package managers with isolated dependencies
  # can find the transitive @sentry/cli dependency.
  RESOLVED_PATH=$(
    "$LOCAL_NODE_BINARY" --print "require('path').dirname(require.resolve('@sentry/cli/package.json', { paths: [require.resolve('@sentry/react-native/package.json')] }))" 2>/dev/null
  ) || true
  if [ -n "$RESOLVED_PATH" ]; then
    SENTRY_CLI_PACKAGE_PATH="$RESOLVED_PATH/bin/sentry-cli"
  else
    # Fallback: parse NODE_PATH from the .bin/sentry-cli shim (file generated by PNPM)
    PNPM_BIN_PATH="$PWD/../node_modules/@sentry/react-native/node_modules/.bin/sentry-cli"

    if [ -f "$PNPM_BIN_PATH" ]; then
      NODE_PATH_VALUE=$(grep -oE 'NODE_PATH="[^"]+"' "$PNPM_BIN_PATH" | head -n1 | sed -E 's/^NODE_PATH="([^"]*)"/\1/')
      # Split on ':' and pick the first entry containing `@sentry/cli/`. Handling each
      # entry separately avoids producing colon-joined garbage when the matching entry
      # isn't the first one in NODE_PATH.
      IFS=':' read -ra NODE_PATH_ENTRIES <<< "$NODE_PATH_VALUE"
      for entry in "${NODE_PATH_ENTRIES[@]}"; do
        if [[ "$entry" == *@sentry/cli/* ]]; then
          SENTRY_CLI_PACKAGE_PATH="${entry%%@sentry/cli/*}@sentry/cli/bin/sentry-cli"
          break
        fi
      done
    fi
  fi
fi
[ -z "$SENTRY_CLI_EXECUTABLE" ] && SENTRY_CLI_EXECUTABLE="$SENTRY_CLI_PACKAGE_PATH"

[[ $SENTRY_INCLUDE_NATIVE_SOURCES == "true" ]] && INCLUDE_SOURCES_FLAG="--include-sources" || INCLUDE_SOURCES_FLAG=""

EXTRA_ARGS="$SENTRY_CLI_EXTRA_ARGS $SENTRY_CLI_DEBUG_FILES_UPLOAD_EXTRA_ARGS $INCLUDE_SOURCES_FLAG"

UPLOAD_DEBUG_FILES="\"$SENTRY_CLI_EXECUTABLE\" debug-files upload $EXTRA_ARGS \"$DWARF_DSYM_FOLDER_PATH\""

# Print a message only when SENTRY_DSYM_DEBUG is set
_sentry_dsym_log() {
  if [ -n "${SENTRY_DSYM_DEBUG}" ]; then
    echo "$1"
  fi
}

# Check if dSYM files are fully generated and ready to upload.
# Returns 0 (ready) or 1 (not ready yet), printing a status message in either case.
_sentry_check_dsym_ready() {
  local dsym_folder="$1"
  local dsym_file_name="$2"
  local attempt="$3"
  local max_attempts="$4"

  if [ ! -d "$dsym_folder" ]; then
    _sentry_dsym_log "dSYM folder does not exist yet: $dsym_folder (attempt $attempt/$max_attempts)"
    return 1
  fi

  local dsym_count
  dsym_count=$(find "$dsym_folder" -name "*.dSYM" -type d 2>/dev/null | wc -l | tr -d ' ')
  if [ "$dsym_count" -eq 0 ]; then
    _sentry_dsym_log "No dSYM bundles found yet in $dsym_folder (attempt $attempt/$max_attempts)"
    return 1
  fi

  echo "Found $dsym_count dSYM bundle(s) in $dsym_folder"

  # DWARF_DSYM_FILE_NAME not set: check if any dSYM has valid DWARF content
  if [ -z "$dsym_file_name" ]; then
    for dsym in "$dsym_folder"/*.dSYM; do
      local dwarf_file
      dwarf_file=$(find "$dsym/Contents/Resources/DWARF" -type f -size +0 2>/dev/null | head -1)
      if [ -n "$dwarf_file" ]; then
        echo "Found dSYM bundle(s) with valid DWARF content"
        return 0
      fi
    done
    _sentry_dsym_log "Found dSYM bundle(s) but none have complete DWARF content yet (attempt $attempt/$max_attempts)"
    return 1
  fi

  # DWARF_DSYM_FILE_NAME set: verify the main app dSYM is complete
  local main_dsym="$dsym_folder/$dsym_file_name"
  if [ ! -d "$main_dsym" ]; then
    _sentry_dsym_log "Main app dSYM not found yet: $dsym_file_name (attempt $attempt/$max_attempts)"
    return 1
  fi

  local dwarf_dir="$main_dsym/Contents/Resources/DWARF"
  if [ ! -d "$dwarf_dir" ]; then
    _sentry_dsym_log "Main app dSYM structure incomplete (missing DWARF directory): $dsym_file_name (attempt $attempt/$max_attempts)"
    return 1
  fi

  local dwarf_files
  dwarf_files=$(find "$dwarf_dir" -type f 2>/dev/null | head -1)
  if [ -z "$dwarf_files" ]; then
    _sentry_dsym_log "Main app dSYM DWARF directory is empty: $dsym_file_name (attempt $attempt/$max_attempts)"
    return 1
  fi

  local dwarf_size
  dwarf_size=$(find "$dwarf_dir" -type f -size +0 2>/dev/null | head -1)
  if [ -z "$dwarf_size" ]; then
    _sentry_dsym_log "Main app dSYM DWARF binary is empty (still being written): $dsym_file_name (attempt $attempt/$max_attempts)"
    return 1
  fi

  echo "Verified main app dSYM is complete: $dsym_file_name"
  return 0
}

# Function to wait for dSYM files to be generated
# This addresses a race condition where the upload script runs before dSYM generation completes
wait_for_dsym_files() {
  local max_attempts="${SENTRY_DSYM_WAIT_MAX_ATTEMPTS:-10}"
  local wait_interval="${SENTRY_DSYM_WAIT_INTERVAL:-2}"
  local attempt=1
  local total_wait_time=0

  # Check if we should wait for dSYM files
  if [ "$SENTRY_DSYM_WAIT_ENABLED" == "false" ]; then
    echo "SENTRY_DSYM_WAIT_ENABLED=false, skipping dSYM wait check"
    return 0
  fi

  # Warn if DWARF_DSYM_FILE_NAME is not set - we can't verify the main app dSYM
  if [ -z "$DWARF_DSYM_FILE_NAME" ]; then
    echo "warning: DWARF_DSYM_FILE_NAME not set, cannot verify main app dSYM specifically"
    echo "warning: Will proceed when any dSYM bundle is found"
  fi

  echo "Checking for dSYM files in: $DWARF_DSYM_FOLDER_PATH"

  # Debug information to help diagnose issues
  _sentry_dsym_log "DEBUG: DWARF_DSYM_FOLDER_PATH=$DWARF_DSYM_FOLDER_PATH"
  _sentry_dsym_log "DEBUG: DWARF_DSYM_FILE_NAME=$DWARF_DSYM_FILE_NAME"
  _sentry_dsym_log "DEBUG: PRODUCT_NAME=$PRODUCT_NAME"

  while [ $attempt -le $max_attempts ]; do
    if _sentry_check_dsym_ready "$DWARF_DSYM_FOLDER_PATH" "$DWARF_DSYM_FILE_NAME" "$attempt" "$max_attempts"; then
      return 0
    fi

    if [ $attempt -lt $max_attempts ]; then
      # Progressive backoff: quick checks first, longer waits later
      # Attempts 1-3: 0.5s (total 1.5s)
      # Attempts 4-6: 1s (total 3s)
      # Attempts 7+: 2s (remaining time)
      local current_interval="$wait_interval"
      if [ -z "${SENTRY_DSYM_WAIT_INTERVAL}" ]; then
        # Only use progressive intervals if user hasn't set custom interval
        if [ $attempt -le 3 ]; then
          current_interval=0.5
        elif [ $attempt -le 6 ]; then
          current_interval=1
        else
          current_interval=2
        fi
      fi

      echo "Waiting ${current_interval}s for dSYM generation to complete..."
      sleep $current_interval
      total_wait_time=$(awk "BEGIN {print $total_wait_time + $current_interval}")
    fi

    attempt=$((attempt + 1))
  done

  # Timeout reached
  echo "warning: Timeout waiting for dSYM files after ${total_wait_time}s ($max_attempts attempts)"
  echo "warning: This may result in incomplete debug symbol uploads"
  echo "warning: To disable this check, set SENTRY_DSYM_WAIT_ENABLED=false"
  return 1
}

XCODE_BUILD_CONFIGURATION="${CONFIGURATION}"

if [ "$SENTRY_DISABLE_AUTO_UPLOAD" == true ]; then
  echo "SENTRY_DISABLE_AUTO_UPLOAD=true, skipping debug files upload"
elif [ "$SENTRY_DISABLE_XCODE_DEBUG_UPLOAD" == true ]; then
  echo "SENTRY_DISABLE_XCODE_DEBUG_UPLOAD=true, skipping native debug files upload"
elif echo "$XCODE_BUILD_CONFIGURATION" | grep -iq "debug"; then # case insensitive check for "debug"
  echo "Skipping debug files upload for *Debug* configuration"
else
  # Wait for dSYM files to be generated (addresses race condition in EAS builds)
  # Don't fail the script if wait times out - we still want to attempt upload
  set +e
  wait_for_dsym_files
  set -e

  # 'warning:' triggers a warning in Xcode, 'error:' triggers an error
  set +x +e # disable printing commands otherwise we might print `error:` by accident and allow continuing on error
  SENTRY_UPLOAD_COMMAND_OUTPUT=$(/bin/sh -c "\"$LOCAL_NODE_BINARY\" $UPLOAD_DEBUG_FILES" 2>&1)
  UPLOAD_EXIT_CODE=$?
  if [ $UPLOAD_EXIT_CODE -eq 0 ]; then
    echo "$SENTRY_UPLOAD_COMMAND_OUTPUT" | awk '{print "output: sentry-cli - " $0}'
  else
    if [ "$SENTRY_ALLOW_FAILURE" == true ]; then
      echo "warning: sentry-cli - Debug files upload failed, but continuing build because SENTRY_ALLOW_FAILURE=true"
      echo "warning: sentry-cli - $SENTRY_UPLOAD_COMMAND_OUTPUT"
    else
      echo "error: sentry-cli - To disable native debug files auto upload, set SENTRY_DISABLE_AUTO_UPLOAD=true in your environment variables. Or to allow failing upload, set SENTRY_ALLOW_FAILURE=true"
      echo "error: sentry-cli - $SENTRY_UPLOAD_COMMAND_OUTPUT"
    fi
  fi
  set -x -e # re-enable
fi
