#!/bin/bash
# © 2023-2026 Emma (prpl.wtf)

# Learn more @ blueprint.zip
# Source code available at github.com/blueprintframework/framework
# Transparent financials available at hcb.hackclub.com/blueprint

# To make changes to the variables persist between updates, make a .blueprintrc file
# and override the variables there.

BLUEPRINT_ENGINE="solstice"
REPOSITORY="BlueprintFramework/framework"
REPOSITORY_BRANCH="main"
VERSION="beta-2026-06" #;

FOLDER=$(realpath "$(dirname "$0" 2> /dev/null)" 2> /dev/null) || FOLDER="$BLUEPRINT__FOLDER"
OWNERSHIP="www-data:www-data" #;
WEBUSER="www-data" #;
USERSHELL="/bin/bash" #;
SHORTCUT_DIR="/usr/local/bin"

# Check if the script is being sourced - and if so - load bash autocompletion.
if [[ "${BASH_SOURCE[0]}" != "${0}" ]]; then
  _blueprint_completions() {
    local cur cmd opts
    COMPREPLY=()
    cur="${COMP_WORDS[COMP_CWORD]}"
    cmd="${COMP_WORDS[1]}"

    case "${cmd}" in
      -install|-add|-i|-query|-q)
        opts="$(
          find "$BLUEPRINT__FOLDER"/*.blueprint 2> /dev/null |
          sed -e "s|^$BLUEPRINT__FOLDER/||g" -e "s|.blueprint$||g"
        )"
      ;;
      -remove|-r)
        opts="$(
          sed -e "s~|~~g" -e "s|,| |g" "$BLUEPRINT__FOLDER/.blueprint/extensions/blueprint/private/db/installed_extensions"
        )"
      ;;
      -export) opts="expose" ;;
      -upgrade) opts="remote" ;;

      *) opts="-install -add -remove -query -init -build -export -wipe -version -help -info -debug -upgrade -unlock -rerun-install -dist" ;;
    esac

    if [[ ${cur} == * ]]; then
      # shellcheck disable=SC2207
      COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
      return 0
    fi
  }
  complete -F _blueprint_completions blueprint
  return 0
fi

# Set Blueprint environment variables.
export BLUEPRINT__FOLDER=$FOLDER
export BLUEPRINT__VERSION=$VERSION
export BLUEPRINT__DEBUG="$FOLDER"/.blueprint/extensions/blueprint/private/debug/logs.txt
export NODE_OPTIONS="--openssl-legacy-provider"

# Defaults
D_OWNERSHIP="www-data:www-data"
D_WEBUSER="www-data"
D_USERSHELL="/bin/bash"

# Check for panels that are using Docker, which should have better support in the future.
if [[ -f "/.dockerenv" ]]; then
  DOCKER="y"
  FOLDER="/app"
else
  DOCKER="n"
fi

source "$FOLDER/.blueprintrc" 2> /dev/null

# This has caused a bunch of errors but is just here to make sure people actually upload the
# "blueprint" folder onto their panel when installing Blueprint. Pick your poison.
if [[ -d "$FOLDER/blueprint" ]]; then mv "$FOLDER/blueprint" "$FOLDER/.blueprint"; fi

if [[ $VERSION != "" ]]; then
  # This function makes sure some placeholders get replaced with the current Blueprint version.
  if [[ ! -f "$FOLDER/.blueprint/extensions/blueprint/private/db/version" ]]; then
    sed -E -i "s*::v*$VERSION*g" "$FOLDER/app/BlueprintFramework/Services/PlaceholderService/BlueprintPlaceholderService.php"
    sed -E -i "s*::v*$VERSION*g" "$FOLDER/.blueprint/extensions/blueprint/public/index.html"
    touch "$FOLDER/.blueprint/extensions/blueprint/private/db/version"
  fi
fi

# Set internal variables.
__BuildDir=".blueprint/extensions/blueprint/private/build"

# Automatically navigate to the Pterodactyl directory when running the script.
cd "$FOLDER" || return

# Import libraries.
source scripts/libraries/parse_yaml.sh    || missinglibs+="[parse_yaml]"
source scripts/libraries/grabenv.sh       || missinglibs+="[grabenv]"
source scripts/libraries/logFormat.sh     || missinglibs+="[logFormat]"
source scripts/libraries/misc.sh          || missinglibs+="[misc]"
source scripts/libraries/lock.sh          || missinglibs+="[lock]"


cdhalt() { PRINT FATAL "Attempted navigation into nonexistent directory, halting process."; exit 1; }
depend() {
  # Make sure Node.js is version 22 or higher.
  nodeMajor=$(node -v | awk -F. '{print $1}' | sed 's/[^0-9]*//g')

  # Check for required (both internal and external) dependencies.
  if \
  ! [ -x "$(command -v unzip)" ] ||                                               # unzip
  ! [ -x "$(command -v yarn)" ] ||                                                # yarn
  ! [ -x "$(command -v zip)" ] ||                                                 # zip
  ! [ -x "$(command -v curl)" ] ||                                                # curl
  ! [ -x "$(command -v php)" ] ||                                                 # php
  ! [ -x "$(command -v git)" ] ||                                                 # git
  ! [ -x "$(command -v grep)" ] ||                                                # grep
  ! [ -x "$(command -v sed)" ] ||                                                 # sed
  ! [ -x "$(command -v awk)" ] ||                                                 # awk
  ! [ -x "$(command -v tput)" ] ||                                                # tput
  ! [ -x "$(command -v node)" ] ||                                                # node
  { ! [ -x "$(command -v inotifywait)" ] && [[ "$DeveloperWatch" == true ]]; } || # inotify-tools (devdep)
  [[ $nodeMajor -lt 22 ]] ||                                                      # node version
  ! [ "$(ls "node_modules/"*"webpack"* 2> /dev/null)"   ] ||                      # webpack
  ! [ "$(ls "node_modules/"*"react"* 2> /dev/null)"     ] ||                      # react
  [[ $missinglibs != "" ]]; then                                                  # internal
    DEPEND_MISSING=true
  fi

  # Exit when missing dependencies.
  if [[ $DEPEND_MISSING == true ]]; then
    PRINT FATAL "Some framework dependencies couldn't be found or have issues. This is usually NOT a bug, do not report it as such."

    if [[ $nodeMajor -lt 22 ]]; then
      PRINT FATAL "Unsupported dependency \"node\" <22.x. (Requires >22.x)"
    fi

    if ! [ -x "$(command -v unzip)"                        ]; then PRINT FATAL "Missing dependency \"unzip\".";   fi
    if ! [ -x "$(command -v node)"                         ]; then PRINT FATAL "Missing dependency \"node\".";    fi
    if ! [ -x "$(command -v yarn)"                         ]; then PRINT FATAL "Missing dependency \"yarn\".";    fi
    if ! [ -x "$(command -v zip)"                          ]; then PRINT FATAL "Missing dependency \"zip\".";     fi
    if ! [ -x "$(command -v curl)"                         ]; then PRINT FATAL "Missing dependency \"curl\".";    fi
    if ! [ -x "$(command -v php)"                          ]; then PRINT FATAL "Missing dependency \"php\".";     fi
    if ! [ -x "$(command -v git)"                          ]; then PRINT FATAL "Missing dependency \"git\".";     fi
    if ! [ -x "$(command -v grep)"                         ]; then PRINT FATAL "Missing dependency \"grep\".";    fi
    if ! [ -x "$(command -v sed)"                          ]; then PRINT FATAL "Missing dependency \"sed\".";     fi
    if ! [ -x "$(command -v awk)"                          ]; then PRINT FATAL "Missing dependency \"awk\".";     fi
    if ! [ -x "$(command -v tput)"                         ]; then PRINT FATAL "Missing dependency \"tput\".";    fi
    if ! [ "$(ls "node_modules/"*"webpack"* 2> /dev/null)" ]; then PRINT FATAL "Missing dependency \"webpack\". Forgot to run 'yarn install'?"; fi
    if ! [ "$(ls "node_modules/"*"react"* 2> /dev/null)"   ]; then PRINT FATAL "Missing dependency \"react\". Forgot to run 'yarn install'?"; fi

    if ! [ -x "$(command -v inotifywait)" ] && [[ "$DeveloperWatch" == true ]]; then
      PRINT FATAL "Developer dependency \"inotify-tools\" is not installed or detected."
    fi

    if [[ $missinglibs == *"[parse_yaml]"*    ]]; then PRINT FATAL "Missing internal dependency \"internal:parse_yaml\"."; fi
    if [[ $missinglibs == *"[grabEnv]"*       ]]; then PRINT FATAL "Missing internal dependency \"internal:grabEnv\".";    fi
    if [[ $missinglibs == *"[logFormat]"*     ]]; then PRINT FATAL "Missing internal dependency \"internal:logFormat\".";  fi
    if [[ $missinglibs == *"[misc]"*          ]]; then PRINT FATAL "Missing internal dependency \"internal:misc\".";       fi
    if [[ $missinglibs == *"[lock]"*          ]]; then PRINT FATAL "Missing internal dependency \"internal:lock\".";       fi

    exit 1
  fi
}

# Assign variables for extension flags.
assignflags() {
  F_ignorePlaceholders=false
  F_forceLegacyPlaceholders=false
  F_developerIgnoreInstallScript=false
  F_developerIgnoreRebuild=false
  F_developerKeepApplicationCache=false
  F_developerEscalateInstallScript=false
  F_developerEscalateExportScript=false
  if [[ ( $flags == *"ignorePlaceholders,"*             ) || ( $flags == *"ignorePlaceholders"             ) ]]; then F_ignorePlaceholders=true             ;fi
  if [[ ( $flags == *"forceLegacyPlaceholders,"*        ) || ( $flags == *"forceLegacyPlaceholders"        ) ]]; then F_forceLegacyPlaceholders=true        ;fi
  if [[ ( $flags == *"developerIgnoreInstallScript,"*   ) || ( $flags == *"developerIgnoreInstallScript"   ) ]]; then F_developerIgnoreInstallScript=true   ;fi
  if [[ ( $flags == *"developerIgnoreRebuild,"*         ) || ( $flags == *"developerIgnoreRebuild"         ) ]]; then F_developerIgnoreRebuild=true         ;fi
  if [[ ( $flags == *"developerKeepApplicationCache,"*  ) || ( $flags == *"developerKeepApplicationCache"  ) ]]; then F_developerKeepApplicationCache=true  ;fi
  if [[ ( $flags == *"developerEscalateInstallScript,"* ) || ( $flags == *"developerEscalateInstallScript" ) ]]; then F_developerEscalateInstallScript=true ;fi
  if [[ ( $flags == *"developerEscalateExportScript,"*  ) || ( $flags == *"developerEscalateExportScript"  ) ]]; then F_developerEscalateExportScript=true  ;fi


  warn_deprecated_flag() { PRINT WARNING "Extension flag '$1' is deprecated."; }

  F_hasInstallScript=false
  if [[ ( $flags == *"hasInstallScript,"* ) || ( $flags == *"hasInstallScript" ) ]]; then
    warn_deprecated_flag "hasInstallScript"
    F_hasInstallScript=true
  fi

  F_hasRemovalScript=false
  if [[ ( $flags == *"hasRemovalScript,"* ) || ( $flags == *"hasRemovalScript" ) ]]; then
    warn_deprecated_flag "hasRemovalScript"
    F_hasRemovalScript=true
  fi

  F_hasExportScript=false
  if [[ ( $flags == *"hasExportScript,"* ) || ( $flags == *"hasExportScript" ) ]]; then
    warn_deprecated_flag "hasExportScript"
    F_hasExportScript=true
  fi

  F_developerForceMigrate=false
  if [[ ( $flags == *"developerForceMigrate,"* ) || ( $flags == *"developerForceMigrate" ) ]]; then
    warn_deprecated_flag "developerForceMigrate"
    F_developerForceMigrate=true
  fi
}

# Adds the "blueprint" command to the $SHORTCUT_DIR and configures the correct permissions for it.
placeshortcut() {
  if [[ $SHORTCUT_DIR != "" ]]; then
    PRINT INFO "Placing Blueprint command shortcut.."

    rm -f scripts/helpers/blueprint.bak
    cp "scripts/helpers/blueprint" "scripts/helpers/blueprint.bak"
    sed -i "s~BLUEPRINT_FOLDER_HERE~$FOLDER~g" "scripts/helpers/blueprint.bak"

    rm -f "$SHORTCUT_DIR/blueprint"
    mv scripts/helpers/blueprint.bak "$SHORTCUT_DIR/blueprint"

    {
      chmod 755 \
        "$FOLDER/blueprint.sh" \
        "$SHORTCUT_DIR/blueprint"
    } >> "$BLUEPRINT__DEBUG"
  else
    PRINT DEBUG "Shortcut not placed as \$SHORTCUT_DIR is empty"
  fi
}
if ! [ -x "$(command -v blueprint)" ]; then placeshortcut; fi


if [[ $1 != "-bash" ]]; then
  if [ -f "$FOLDER/.blueprint/extensions/blueprint/private/db/is_installed" ]; then
    PRINT FATAL "Blueprint is already installed, use the 'blueprint' command instead."
    exit 2
  else
    # Only run if Blueprint is not in the process of upgrading.
    if [[ $BLUEPRINT_ENVIRONMENT != "upgrade2" ]]; then
      # Print Blueprint icon with ascii characters.
      C0="\x1b[0m"
      C1="\x1b[31;43;1m"
      C2="\x1b[32;44;1m"
      C3="\x1b[34;45;1m"
      C3="\x1b[0;37;1m"
      echo -e "$C0" \
        "\n$C4  ██$C1▌$C2▌$C3▌$C0   Blueprint Framework" \
        "\n$C4██  ██$C1▌$C2▌$C3▌$C0 https://blueprint.zip" \
        "\n$C4  ████$C1▌$C2▌$C3▌$C0 © 2023-2026 Emma (prpl.wtf)\n";

      export PROGRESS_TOTAL=15
      export PROGRESS_NOW=0
    else
      PROGRESS_TOTAL="$(("$PROGRESS_TOTAL" + 15))"
    fi

    if [[ $BLUEPRINT_ENVIRONMENT == "upgrade" ]]; then
      echo -e "\x1b[37;41;1m MANUAL ACTION REQUIRED \x1b[0m"
      echo -e "\n\x1b[31;49;1mThis is NOT a bug. Please follow the following instructions:\x1b[0m"

      echo -e "\n\x1b[31;49m1. Use the CTRL+C (^C) keyboard shortcut to terminate this process.\x1b[0m"
      echo -e "\x1b[31;49m2. Then, run 'blueprint -upgrade' AGAIN.\x1b[0m"

      read -r
      exit 1
    fi

    if [[ $BLUEPRINT_ENVIRONMENT == "upgrade2" ]]; then
      # Get rid of beta-2025-11 leftovers
      rm -rf resources/scripts/blueprint/utility resources/scripts/blueprint/css/BlueprintStylesheet.css resources/scripts/blueprint/index.ts
    fi

    if [[ $BLUEPRINT_ENVIRONMENT != "ci" ]]; then
      PRINT INFO "Installing node modules.."
      # Check for yarn before installing node modules..
      if ! [ -x "$(command -v yarn)" ]; then
        PRINT FATAL "Missing dependency \"yarn\"."
      fi
      hide_progress
    fi

    if [[ $BLUEPRINT_ENVIRONMENT != "ci" ]]; then
      set -eo pipefail
      yarn install
      set +eo pipefail
    fi

    ((PROGRESS_NOW++))

    PRINT INFO "Searching and validating framework dependencies.."
    depend # Check if required dependencies are installed

    ((PROGRESS_NOW++))

    if [[ $BLUEPRINT_ENVIRONMENT != "ci" ]]; then
      if \
      ! [ "$OWNERSHIP" = "$D_OWNERSHIP" ] ||
      ! [ "$WEBUSER"   = "$D_WEBUSER"   ] ||
      ! [ "$USERSHELL" = "$D_USERSHELL" ]; then
        PRINT WARNING "Blueprint variable customization is deprecated, autogenerating .blueprintrc file.."
        if [ -f "$FOLDER/.blueprintrc" ]; then
          PRINT WARNING "Could not autogenerate .blueprintrc file, already exists."
        else
          echo -e \
            "OWNERSHIP=\"$OWNERSHIP\"\n" \
            "WEBUSER=\"$WEBUSER\"\n" \
            "USERSHELL=\"$USERSHELL\"" \
            > "$FOLDER/.blueprintrc"
          PRINT INFO "Autogenerated .blueprintrc file."
        fi
      fi
    fi

    ((PROGRESS_NOW++))

    placeshortcut # Place Blueprint shortcut

    ((PROGRESS_NOW++))

    # Link directories.
    PRINT INFO "Linking directories and filesystems.."
    {
      ln -s -r -T "$FOLDER/.blueprint/extensions/blueprint/public" "$FOLDER/public/extensions/blueprint"
      ln -s -r -T "$FOLDER/.blueprint/extensions/blueprint/assets" "$FOLDER/public/assets/extensions/blueprint"
      ln -s -r -T "$FOLDER/scripts/libraries" "$FOLDER/.blueprint/lib"
    } 2>> "$BLUEPRINT__DEBUG"
    php artisan storage:link &>> "$BLUEPRINT__DEBUG"

    ((PROGRESS_NOW++))

    # Copy "Blueprint" extension page logo from assets.
    cp "$FOLDER/.blueprint/assets/Emblem/emblem.jpg" "$FOLDER/.blueprint/extensions/blueprint/assets/logo.jpg"

    ((PROGRESS_NOW++))

    # Put application into maintenance.
    if [[ $BLUEPRINT_ENVIRONMENT != "ci" ]]; then
      PRINT INPUT "Would you like to put your application into maintenance while Blueprint is installing? (Y/n)"
      hide_progress
      read -r YN
      if [[ ( $YN == "y"* ) || ( $YN == "Y"* ) || ( $YN == "" ) ]]; then
        MAINTENANCE="true"
        PRINT INFO "Put application into maintenance mode."
        php artisan down &>> "$BLUEPRINT__DEBUG"
      else
        MAINTENANCE="false"
        PRINT INFO "Putting application into maintenance has been skipped."
      fi
    else
      MAINTENANCE="false"
      PRINT INFO "Putting application into maintenance has been skipped."
    fi

    ((PROGRESS_NOW++))

    # Run migrations if Blueprint is not running through Docker.
    if [[ ( $DOCKER != "y" ) && ( $BLUEPRINT_ENVIRONMENT != "ci" ) ]]; then
      PRINT INFO "Running database migrations.."
      hide_progress
      php artisan migrate --force
    fi

    ((PROGRESS_NOW++))

    # Seed Blueprint database records
    if [[ $BLUEPRINT_ENVIRONMENT != "ci" ]]; then
      PRINT INFO "Seeding Blueprint database records.."
      php artisan db:seed --class=BlueprintSeeder --force &>> "$BLUEPRINT__DEBUG"
    fi

    ((PROGRESS_NOW++))

    # Flush cache.
    if [[ $BLUEPRINT_ENVIRONMENT != "ci" ]]; then
      PRINT INFO "Flushing cache.."
      {
        php artisan view:cache
        php artisan config:cache
        php artisan route:clear
        php artisan cache:clear
        php artisan bp:cache
        php artisan bp:version:cache
      } &>> "$BLUEPRINT__DEBUG"
    fi

    ((PROGRESS_NOW++))

    # Restart queue workers
    PRINT INFO "Restarting queue workers.."
    php artisan queue:restart &>> "$BLUEPRINT__DEBUG"

    ((PROGRESS_NOW++))

    # Make sure all files have correct permissions.
    if [[ $BLUEPRINT_ENVIRONMENT != "ci" ]]; then
      PRINT INFO "Changing Pterodactyl file ownership to '$OWNERSHIP'.."
      find "$FOLDER/" \
        -path "$FOLDER/node_modules" -prune \
        -o -exec chown "$OWNERSHIP" {} + &>> "$BLUEPRINT__DEBUG"
    fi

    ((PROGRESS_NOW++))

    PRINT INFO "Cleaning up.."
    rm -f \
      ".blueprint/dev/.gitkeep" \
      ".blueprint/dist/types/.gitkeep"

    ((PROGRESS_NOW++))

    # Rebuild panel assets.
    if [[ $BLUEPRINT_ENVIRONMENT != "ci" ]]; then
      PRINT INFO "Rebuilding panel assets.."
      hide_progress
      cd "$FOLDER" || cdhalt
      set -eo pipefail
      rm -rf "$FOLDER/node_modules/.cache"
      yarn run build:production --progress
      set +eo pipefail
    fi

    ((PROGRESS_NOW++))

    if [[ $MAINTENANCE == "true" ]]; then
      # Put application into production.
      PRINT INFO "Put application into production."
      php artisan up &>> "$BLUEPRINT__DEBUG"
    fi

    ((PROGRESS_NOW++))

    # Let the panel know the user has finished installation.
    if [ ! -f "$FOLDER/.blueprint/extensions/blueprint/private/db/is_installed" ]; then
      touch "$FOLDER/.blueprint/extensions/blueprint/private/db/is_installed"
    fi

    sed -i "s~NOTINSTALLED~INSTALLED~g" "$FOLDER/app/BlueprintFramework/Services/PlaceholderService/BlueprintPlaceholderService.php"

    # Finish installation
    if [[ $BLUEPRINT_ENVIRONMENT != "upgrade2" ]]; then
      PRINT SUCCESS "Blueprint has completed its installation process."
      hide_progress
    fi

    exit 0
  fi
fi

Command() {
  PRINT FATAL "'$cmd' is not a valid command or argument. Use argument '-help' for a list of commands."
}

cmd="${2}"
case "$cmd" in
  -add|-install|-i) source ./scripts/commands/extensions/install.sh ;;
  -remove|-r) source ./scripts/commands/extensions/remove.sh ;;
  -query|-q) source ./scripts/commands/extensions/query.sh ;;
  -init|-I) source ./scripts/commands/developer/init.sh ;;
  -build|-b) source ./scripts/commands/developer/build.sh ;;
  -watch) source ./scripts/commands/developer/watch.sh ;;
  -dist) source ./scripts/commands/developer/dist.sh ;;
  -export|-e) source ./scripts/commands/developer/export.sh ;;
  -wipe|-w) source ./scripts/commands/developer/wipe.sh ;;
  -info|-f) source ./scripts/commands/misc/info.sh ;;
  -debug) source ./scripts/commands/misc/debug.sh ;;
  -help|-h|help|'') source ./scripts/commands/misc/help.sh ;;
  -version|-v) source ./scripts/commands/misc/version.sh ;;
  -rerun-install) source ./scripts/commands/advanced/rerun-install.sh ;;
  -upgrade) source ./scripts/commands/advanced/upgrade.sh ;;
  -unlock) source ./scripts/commands/advanced/unlock.sh ;;
esac

shift 2

# prevent interesting freakout when passing "*" as an argument
for arg in "$@"; do
  if [[ "$arg" == "*" ]]; then
    PRINT FATAL "\"*\" cannot be used as an argument."
    exit 2
  fi
done

Command "$@"
exit 0
