#!/usr/bin/env bash
#
# Builds a Galaxy binary and bundles it up for distribution.

set -e

WORKSPACE_ROOT_DIR="$(pwd)"

cleanup() {
  # Delete the directory that was used as a staging area during the bundling
  # process.
  if [ -d "$DIST_DIR" ]; then
    rm -rf "$DIST_DIR"
  fi
}

# Run cleanup() when the script terminates (whether it succeeded or failed).
trap cleanup EXIT

# Build the OSS bundle.
RELEASE_CHANNEL="oss"
FEATURES="release_bundle"
EXTRA_FEATURES=""
PACKAGES=( appimage )
BUILD="true"
BUILD_ARCH="$(uname -m)"
DEBUG=false
ARTIFACT="app"

# Cache all params so we can pass them to downstream scripts.
ALL_PARAMS=$@

PARAMS=""
while (( "$#" )); do
  case "$1" in
    --debug)
      DEBUG=true
      shift
      ;;
    --check-only)
      echo 'Only running `cargo check` and not producing a bundle.'
      CHECK_ONLY="true"
      shift
      ;;
    --skip-build)
      BUILD="false"
      shift
      ;;
    --nouniversal)
      # Discard the --nouniversal argument, which is only used for macOS
      # bundles.
      shift
      ;;
    -c|--channel)
      if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then
        RELEASE_CHANNEL=$2
        shift 2
      else
        echo "Error: Argument for $1 is missing" >&2
        exit 1
      fi
      ;;
    --release-tag)
      if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then
        echo "Setting release tag to $2"
        export GIT_RELEASE_TAG=$2
        shift 2
      else
        echo "Error: Argument for $1 is missing" >&2
        exit 1
      fi
      ;;
    --packages)
      PACKAGES=( $(IFS=, ; echo $2) )
      shift 2
      ;;
    --features)
      if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then
        EXTRA_FEATURES="$2"
        shift 2
      else
        echo "Error: Argument for $1 is missing" >&2
        exit 1
      fi
      ;;
    --artifact)
      if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then
        if [[ "$2" != "app" && "$2" != "cli" && "$2" != "galaxyctrl" ]]; then
          echo "Error: --artifact must be 'app', 'cli', or 'galaxyctrl', got '$2'" >&2
          exit 1
        fi
        ARTIFACT="$2"
        shift 2
      else
        echo "Error: Argument for $1 is missing" >&2
        exit 1
      fi
      ;;
    --arch)
      if [ -n "$2" ]; then
        if [ "$2" = "aarch64" -o "$2" = "x86_64" ]; then
          echo "Setting architecture to $2"
          export BUILD_ARCH=$2
          shift 2
        else
          echo "Error: Argument for $1 is invalid; got '$2' but expected 'aarch64' or 'x86_64'." >&2
          exit 1
        fi
      else
        echo "Error: Argument for $1 is missing" >&2
        exit 1
      fi
      ;;
    *) # preserve positional arguments
      PARAMS="$PARAMS $1"
      shift
      ;;
  esac
done
# set positional arguments in their proper place
eval set -- "$PARAMS"

# Statically compile the CLI and galaxyctrl artifacts so they can run on older Linux distros.
if [[ "$ARTIFACT" == "cli" || "$ARTIFACT" == "galaxyctrl" ]]; then
  TARGET_TRIPLE="${BUILD_ARCH}-unknown-linux-musl"
  # Only configure the musl toolchain when we are actually compiling.  Packaging-only
  # runs (--skip-build) just need TARGET_TRIPLE to locate the prebuilt binary, and
  # configuring the toolchain there would force an unnecessary (and potentially
  # failing) musl-cross download on hosts that never compile.
  if [[ "$BUILD" == "true" ]]; then
    source "$WORKSPACE_ROOT_DIR/script/linux/configure_musl_toolchain" "$TARGET_TRIPLE"
    # Make sure we have the rust musl target available.
    rustup target add "$TARGET_TRIPLE"
  fi
fi
CARGO_TARGET_DIR="${CARGO_TARGET_DIR:-$WORKSPACE_ROOT_DIR/target}"
CARGO_TARGET_OUTPUT_ROOT="$CARGO_TARGET_DIR${TARGET_TRIPLE:+/$TARGET_TRIPLE}"

DIST_DIR="$CARGO_TARGET_OUTPUT_ROOT/dist"

if [[ $DEBUG = true ]]; then
  CARGO_PROFILE="dev"
elif [[ "$ARTIFACT" == "cli" || "$ARTIFACT" == "galaxyctrl" ]]; then
  CARGO_PROFILE="release-cli"
else
  CARGO_PROFILE="release-lto"
fi

if [[ "$CARGO_PROFILE" == "dev" ]]; then
  CARGO_TARGET_OUTPUT_DIR="$CARGO_TARGET_OUTPUT_ROOT/debug"
else
  CARGO_TARGET_OUTPUT_DIR="$CARGO_TARGET_OUTPUT_ROOT/$CARGO_PROFILE"
fi
# NOTE: if you change this path, update the "Clean stale bundle output"
# steps in .github/workflows/create_release.yml so they continue to wipe
# stale packages left in the shared Namespace runner cache.
OUT_DIR="$CARGO_TARGET_OUTPUT_DIR/bundle/linux"
mkdir -p "$OUT_DIR"

if [[ "$RELEASE_CHANNEL" != "oss" ]]; then
  echo "Error: only the oss release channel is supported" >&2
  exit 1
fi

# OSS is the only supported release channel.
GALAXY_BIN="galaxy-oss"
BINARY_NAME="galaxy-oss"
APP_NAME="GalaxyOss"

# Artifact-specific binary naming
if [[ "$ARTIFACT" == "cli" ]]; then
  # Keep packaged CLI names aligned with `Channel::cli_command_name`.
  BINARY_NAME="galaxy-ai-oss"
elif [[ "$ARTIFACT" == "galaxyctrl" ]]; then
  BINARY_NAME="galaxyctrl"
  PACKAGES=()
fi

# Artifact-specific configuration
if [[ "$ARTIFACT" == "cli" || "$ARTIFACT" == "galaxyctrl" ]]; then
  FEATURES="$FEATURES,standalone"
  if [[ "$ARTIFACT" == "galaxyctrl" ]]; then
    FEATURES="$FEATURES,galaxy_control_cli"
  fi
elif [[ "$ARTIFACT" == "app" ]]; then
  # All channels ship the v3 classifier and v2 heuristic.
  FEATURES="$FEATURES,gui,nld_classifier_v3,nld_heuristic_v2,galaxy_control_cli"
fi
if [[ -n "$EXTRA_FEATURES" ]]; then
  FEATURES="$FEATURES,$EXTRA_FEATURES"
fi

BUNDLE_ID="com.galaxy.GalaxyOss"
EXECUTABLE_PATH="$CARGO_TARGET_OUTPUT_DIR/$GALAXY_BIN"
DEBUG_EXECUTABLE_PATH="$EXECUTABLE_PATH.debug"

# Note that this variable must be set (and exported!) before we compile the
# binary, as it is read at compile time by Linux autoupdate logic (to know the
# expected name of the AppImage when downloading updates).
export APPIMAGE_NAME="$APP_NAME-$BUILD_ARCH.AppImage"

# If we only want to check that compilation will succeed, perform the checks
# then exit.  We use this script to invoke `cargo check` to ensure that we are
# using the same feature flags and profile that we would be using in production.
if [[ "$CHECK_ONLY" == "true" ]]; then
  cargo check -p galaxy --profile "$CARGO_PROFILE" --bin "$GALAXY_BIN" --features "$FEATURES" ${TARGET_TRIPLE:+--target $TARGET_TRIPLE}
  exit 0
fi

# Build the binary.
if [[ "$BUILD" == "true" ]]; then
  echo "Building and bundling Galaxy for channel $RELEASE_CHANNEL and bundle id $BUNDLE_ID"
  cargo build -p galaxy --profile "$CARGO_PROFILE" --bin "$GALAXY_BIN" --features "$FEATURES" ${TARGET_TRIPLE:+--target $TARGET_TRIPLE}

  echo "Making debug copy of '$EXECUTABLE_PATH' at '$DEBUG_EXECUTABLE_PATH'"
  cp "$EXECUTABLE_PATH" "$DEBUG_EXECUTABLE_PATH"

  echo "Stripping debug symbols from '$EXECUTABLE_PATH'"
  # Strip all symbols to keep the shipped OSS binary small.
  "${WARP_MUSL_STRIP:-strip}" --strip-all "$EXECUTABLE_PATH"
else
  echo 'Skipping `cargo build` step due to --skip-build argument'
fi

if [[ "$ARTIFACT" == "galaxyctrl" ]]; then
  echo "Copying control-mode binary into $OUT_DIR/$GALAXY_BIN"
  cp "$EXECUTABLE_PATH" "$OUT_DIR/$GALAXY_BIN"
  GALAXYCTRL_SCRIPT_PATH="$OUT_DIR/galaxyctrl"
  echo "Creating galaxyctrl wrapper script at $GALAXYCTRL_SCRIPT_PATH"
  cat > "$GALAXYCTRL_SCRIPT_PATH" << EOF
#!/usr/bin/env bash
script_dir="\$(cd "\$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
exec -a "\$0" "\$script_dir/$GALAXY_BIN" --galaxyctrl "\$@"
EOF
  chmod +x "$GALAXYCTRL_SCRIPT_PATH"
fi
BINARY_PATH="$EXECUTABLE_PATH"
if [[ "$ARTIFACT" == "galaxyctrl" ]]; then
  BINARY_PATH="$GALAXYCTRL_SCRIPT_PATH"
fi

# Prepare bundled resources for CLI builds.
if [[ "$ARTIFACT" == "cli" || "$ARTIFACT" == "galaxyctrl" ]]; then
  echo "Preparing CLI resources directory"
  BUNDLED_RESOURCES_DIR="$OUT_DIR/resources"
  "$WORKSPACE_ROOT_DIR/script/prepare_bundled_resources" "$BUNDLED_RESOURCES_DIR" "$RELEASE_CHANNEL" "$CARGO_PROFILE"
fi


# If this is being run within a GitHub action, set an output variable with the
# location of the binary so it can be referenced by subsequent actions, as well
# as the directory containing all built packages.
if [ "${GITHUB_ACTIONS}" == "true" ]; then
  echo "::echo::on"
  echo "executable_path=$BINARY_PATH" >> "$GITHUB_OUTPUT"
  echo "debug_executable_path=$DEBUG_EXECUTABLE_PATH" >> "$GITHUB_OUTPUT"
  echo "packages_dir=$OUT_DIR" >> "$GITHUB_OUTPUT"
  echo "bundled_resources_dir=${BUNDLED_RESOURCES_DIR:-}" >> "$GITHUB_OUTPUT"
  echo "::echo::off"
fi

# Make sure a variety of environment variables are available in downstream scripts.
export \
  WORKSPACE_ROOT_DIR \
  DIST_DIR \
  CARGO_TARGET_OUTPUT_DIR \
  OUT_DIR \
  RELEASE_CHANNEL \
  BUNDLE_ID \
  EXECUTABLE_PATH \
  BINARY_NAME \
  APPIMAGE_NAME \
  BUILD_ARCH \
  ARTIFACT \
  CARGO_PROFILE

# Build the AppImage bundle.
if [[ ${PACKAGES[@]} =~ "appimage" ]]; then
  echo "Building AppImage..."
  "$WORKSPACE_ROOT_DIR/script/linux/bundle_appimage"
fi

# Build the .deb package.
if [[ ${PACKAGES[@]} =~ "deb" ]]; then
  echo "Building .deb package..."
  "$WORKSPACE_ROOT_DIR/script/linux/bundle_deb"
fi

# Build the .rpm package.
if [[ ${PACKAGES[@]} =~ "rpm" ]]; then
  echo "Building .rpm package..."
  "$WORKSPACE_ROOT_DIR/script/linux/bundle_rpm"
fi

# Build the Arch Linux package.
if [[ ${PACKAGES[@]} =~ "arch" ]]; then
  echo "Building Arch Linux package..."
  "$WORKSPACE_ROOT_DIR/script/linux/bundle_arch"
fi
