#!/bin/sh
# SecDim Quick Lab — clone a hands-on security lab and get started in
# under a minute. POSIX sh, no external dependencies beyond git (and
# optionally curl, for the initial download of this script itself).
#
# Usage:
#   curl -fsSL https://secdim.com/quick-lab.sh | sh

set -eu

QL_ESC=$(printf '\033')
readonly QL_ESC

QL_LAB_HOST="https://lab.secdim.com"
QL_LAB_SLUG="numeric-overflow"
readonly QL_LAB_HOST QL_LAB_SLUG

quick_lab_main() {
  quick_lab_colors
  quick_lab_detect_sleep_support
  quick_lab_splash

  quick_lab_explain_and_confirm

  quick_lab_check_environment
  case "$QL_RUNTIME_MODE" in
    docker) : ;;
    runtime)
      quick_lab_select_runtime
      quick_lab_runtime_mode_notice
      ;;
  esac

  quick_lab_clone
  quick_lab_get_started
  quick_lab_sign_off
}

# ----------------------------------------------------------------
# Colours & terminal capability
# ----------------------------------------------------------------

quick_lab_colors() {
  QL_RESET=
  QL_BOLD=
  QL_DIM=
  QL_CYAN=
  QL_GREEN=
  QL_RED=
  QL_WHITE=
  if [ -t 1 ] && [ "${TERM:-}" != "dumb" ]; then
    QL_RESET="${QL_ESC}[0m"
    QL_BOLD="${QL_ESC}[1m"
    QL_DIM="${QL_ESC}[2m"
    QL_GREEN="${QL_ESC}[32m"
    QL_RED="${QL_ESC}[31m"
    QL_WHITE="${QL_ESC}[97m"
    QL_CYAN="${QL_ESC}[96m"
  fi
}

# Some `sleep` builds choke on fractional seconds. Under `set -e` that
# would kill the whole script, not just the animation, so probe once
# and route every delay through quick_lab_sleep().
quick_lab_detect_sleep_support() {
  if sleep 0.01 2>/dev/null; then
    QL_FRACTIONAL_SLEEP=1
  else
    QL_FRACTIONAL_SLEEP=0
  fi
}

quick_lab_sleep() {
  if [ "$QL_FRACTIONAL_SLEEP" = 1 ]; then
    sleep "$1" 2>/dev/null || true
    return 0
  fi
  case "$1" in
    0.*|.*) return 0 ;;
    *) sleep "${1%%.*}" 2>/dev/null || true ;;
  esac
}

quick_lab_supports_unicode() {
  locale="${LC_ALL:-${LC_CTYPE:-${LANG:-}}}"
  case "$locale" in
    *UTF-8*|*utf-8*|*UTF8*|*utf8*) return 0 ;;
  esac
  case "${TERM_PROGRAM:-}" in
    Apple_Terminal|iTerm.app|vscode|WezTerm) return 0 ;;
  esac
  return 1
}

quick_lab_mark_ok() {
  if quick_lab_supports_unicode; then
    printf '%s✓%s' "$QL_GREEN" "$QL_RESET"
  else
    printf '%sok%s' "$QL_GREEN" "$QL_RESET"
  fi
}

quick_lab_mark_bad() {
  if quick_lab_supports_unicode; then
    printf '%s✗%s' "$QL_RED" "$QL_RESET"
  else
    printf '%sx%s' "$QL_RED" "$QL_RESET"
  fi
}

quick_lab_mark_arrow() {
  if quick_lab_supports_unicode; then
    printf '→'
  else
    printf '->'
  fi
}

quick_lab_spinner_frame() {
  step="$1"
  if quick_lab_supports_unicode; then
    case $((step % 10)) in
      0) printf '⠋' ;; 1) printf '⠙' ;; 2) printf '⠹' ;; 3) printf '⠸' ;;
      4) printf '⠼' ;; 5) printf '⠴' ;; 6) printf '⠦' ;; 7) printf '⠧' ;;
      8) printf '⠇' ;; *) printf '⠏' ;;
    esac
  else
    case $((step % 4)) in
      0) printf '-' ;; 1) printf '\\' ;; 2) printf '|' ;; *) printf '/' ;;
    esac
  fi
}

# Spinner while $2 runs in the background; swapped for a check/cross
# mark once it settles.
quick_lab_run_check() {
  check_label="$1"
  check_cmd="$2"

  if [ ! -t 1 ] || [ "${TERM:-}" = "dumb" ]; then
    if eval "$check_cmd" >/dev/null 2>&1; then
      printf '  %s  %s\n' "$(quick_lab_mark_ok)" "$check_label"
      return 0
    else
      printf '  %s  %s\n' "$(quick_lab_mark_bad)" "$check_label"
      return 1
    fi
  fi

  ( eval "$check_cmd" >/dev/null 2>&1 ) &
  check_pid=$!

  step=0
  printf '\033[?25l'
  while kill -0 "$check_pid" 2>/dev/null; do
    printf '\r  %s  %s' "$(quick_lab_spinner_frame "$step")" "$check_label"
    step=$((step + 1))
    quick_lab_sleep 0.08
  done
  printf '\033[?25h'

  if wait "$check_pid"; then
    printf '\r\033[K  %s  %s\n' "$(quick_lab_mark_ok)" "$check_label"
    return 0
  else
    printf '\r\033[K  %s  %s\n' "$(quick_lab_mark_bad)" "$check_label"
    return 1
  fi
}

# ----------------------------------------------------------------
# PHASE 1 — Splash
# ----------------------------------------------------------------

# If you're reading the ASCII art in a shell script's source, respect.
QL_LOGO_LINES='
   %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
   %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
   %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
   %%%%%%%%%%%%%%              %%%%%%%%%%%%
   %%%%%%%%%%%                  ,%%%%%%%%%%
   %%%%%%%%%%         *%%%%*    %%%%%%%%%%%
   %%%%%%%%%%        %%%%%%%%%%%%%%%%%%%%%%
   %%%%%%%%%%%             *%%%%%%%%%%%%%%%
   %%%%%%%%%%%%%               .%%%%%%%%%%%
   %%%%%%%%%%%%%%%%%%            %%%%%%%%%%
   %%%%%%%%%%%%%%%%%%%%%%%        %%%%%%%%%
   %%%%%%%%%%      .%%%%.         %%%%%%%%%
   %%%%%%%%%%                   .%%%%%%%%%%
   %%%%%%%%%%%%,             ,%%%%%%%%%%%%%
   %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
   %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
'

quick_lab_splash() {
  if [ ! -t 1 ] || [ "${TERM:-}" = "dumb" ]; then
    quick_lab_print_logo "$QL_CYAN"
    return
  fi

  clear 2>/dev/null || printf '\033[2J\033[H'
  printf '\033[?25l'
  quick_lab_reveal_logo "$QL_CYAN"
  printf '\033[?25h'
  quick_lab_sleep 0.35

  i=0
  while [ "$i" -lt 2 ]; do
    clear 2>/dev/null || printf '\033[2J\033[H'
    quick_lab_print_logo "$QL_WHITE"
    quick_lab_sleep 0.08
    clear 2>/dev/null || printf '\033[2J\033[H'
    quick_lab_print_logo "$QL_CYAN"
    quick_lab_sleep 0.12
    i=$((i + 1))
  done

  quick_lab_sleep 1.2
}

quick_lab_reveal_logo() {
  color="$1"
  printf '%s' "$color"
  old_ifs=$IFS
  IFS='
'
  for line in $QL_LOGO_LINES; do
    printf '%s\n' "$line"
    quick_lab_sleep 0.025
  done
  IFS=$old_ifs
  printf '%s' "$QL_RESET"
  quick_lab_print_tagline "$color"
}

quick_lab_print_tagline() {
  color="$1"
  printf '   %s%ssecdim.com%s  %s|%s  Find it. Hack it. Fix it.\n\n' \
    "$QL_BOLD" "$color" "$QL_RESET" "$QL_DIM" "$QL_RESET"
}

quick_lab_print_logo() {
  color="$1"
  printf '%s' "$color"
  old_ifs=$IFS
  IFS='
'
  for line in $QL_LOGO_LINES; do
    printf '%s\n' "$line"
  done
  IFS=$old_ifs
  printf '%s' "$QL_RESET"
  quick_lab_print_tagline "$color"
}

# ----------------------------------------------------------------
# PHASE 2 — Explain + confirm
# ----------------------------------------------------------------

quick_lab_explain_and_confirm() {
  printf '%sThis script sets up a free hands-on security lab%s\n' "$QL_BOLD" "$QL_RESET"
  printf 'on your machine.\n\n'

  printf 'But before anything, this is your lesson number one:\n'
  printf 'you must always check the authenticity and integrity of\n'
  printf 'any script you run from the internet.\n\n'

  printf '%sAuthenticity%s — ensure the script is from %shttps://secdim.com%s\n' \
    "$QL_BOLD" "$QL_RESET" "$QL_CYAN" "$QL_RESET"
  printf 'and downloaded over HTTPS with no certificate errors.\n\n'

  printf '%sIntegrity%s — this script is signed by SecDim. Verify the\n' "$QL_BOLD" "$QL_RESET"
  printf 'signature against SecDim'"'"'s public key instead.\n'
  printf 'There is a shield icon next to the download link on secdim.com\n'
  printf 'that walks through it — download the script and its signature,\n'
  printf 'then:\n\n'
  printf '  %sgpg --keyserver hkps://keys.openpgp.org \\%s\n' "$QL_CYAN" "$QL_RESET"
  printf '  %s    --recv-keys 59A1DC49D6D9C94C%s\n' "$QL_CYAN" "$QL_RESET"
  printf '  %sgpg --verify quick-lab.sh.sig quick-lab.sh%s\n\n' "$QL_CYAN" "$QL_RESET"

  quick_lab_confirm "Have you checked the authenticity of this script?" || quick_lab_authenticity_help
  quick_lab_confirm "Have you checked the integrity of this script?" || quick_lab_integrity_help

  printf '\n  %s  %sCongratulations! Lesson one is complete.%s\n\n' \
    "$(quick_lab_mark_ok)" "$QL_GREEN" "$QL_RESET"
  printf 'Remember: always check the authenticity and integrity of a\n'
  printf 'script before running it — never run one from an untrusted\n'
  printf 'source, and never without verifying it first.\n\n'
  printf 'A hash alone is not strong verification: if the script and\n'
  printf 'its hash are hosted on the same server, a compromised host\n'
  printf 'lets an adversary backdoor the script and update the hash to\n'
  printf 'match. They cannot forge the signature file, though — that\n'
  printf 'requires the private key SecDim signed it with, which never\n'
  printf 'leaves SecDim.\n\n'
  printf 'Let'"'"'s continue with the setup.\n\n'

  printf 'The lab: a numeric overflow (integer overflow) vulnerability\n'
  printf 'inspired by the Boeing 787 power generator failure.\n'
  printf 'You will find the flaw, trigger it, and fix it.\n\n'

  printf '%sWhat this script will do:%s\n' "$QL_BOLD" "$QL_RESET"
  printf '  - Check for Git and Docker (or a language runtime)\n'
  printf '  - Clone one lab repository\n'
  printf '  - Point you to the README to get started\n\n'

  quick_lab_confirm "Continue?" || quick_lab_decline
  printf '\n'
}

quick_lab_confirm() {
  prompt="$1"
  printf '%s%s%s [y/N]: ' "$QL_BOLD" "$prompt" "$QL_RESET"
  answer=$(quick_lab_read_line)
  case "$answer" in
    y|Y|yes|YES) return 0 ;;
    *) return 1 ;;
  esac
}

# curl | sh eats stdin feeding the script's own source, so reads have
# to come from /dev/tty instead once that pipe is exhausted.
quick_lab_read_line() {
  if [ -t 0 ]; then
    IFS= read -r line || line=
  elif [ -r /dev/tty ] && [ -w /dev/tty ]; then
    IFS= read -r line < /dev/tty || line=
  else
    line=
  fi
  printf '%s' "$line"
}

quick_lab_decline() {
  printf '\nNo problem. Visit %splay.secdim.com%s anytime.\n' "$QL_CYAN" "$QL_RESET"
  exit 0
}

quick_lab_authenticity_help() {
  printf '\n%sHow to check authenticity:%s\n\n' "$QL_BOLD" "$QL_RESET"
  printf '  1. Confirm the download URL is exactly:\n'
  printf '     %shttps://secdim.com/quick-lab.sh%s\n' "$QL_CYAN" "$QL_RESET"
  printf '  2. Confirm it was fetched over HTTPS with no certificate\n'
  printf '     warnings — curl and browsers already refuse an invalid\n'
  printf '     certificate, so if the download succeeded, this part is met.\n'
  printf '  3. If you'"'"'re unsure, open that URL in a browser first and\n'
  printf '     read the script before piping it into a shell.\n\n'
  printf 'Come back and run this again once you'"'"'ve checked.\n'
  exit 0
}

quick_lab_integrity_help() {
  printf '\n%sHow to check integrity:%s\n\n' "$QL_BOLD" "$QL_RESET"
  printf '  1. Download the script instead of piping it straight into sh —\n'
  printf '     click the shield icon next to the download command on\n'
  printf '     secdim.com for both files:\n'
  printf '     %squick-lab.sh%s, %squick-lab.sh.sig%s\n' \
    "$QL_CYAN" "$QL_RESET" "$QL_CYAN" "$QL_RESET"
  printf '  2. Import SecDim'"'"'s public key:\n'
  printf '     %sgpg --keyserver hkps://keys.openpgp.org \\%s\n' "$QL_CYAN" "$QL_RESET"
  printf '     %s    --recv-keys 59A1DC49D6D9C94C%s\n' "$QL_CYAN" "$QL_RESET"
  printf '  3. Verify the signature:\n'
  printf '     %sgpg --verify quick-lab.sh.sig quick-lab.sh%s\n' "$QL_CYAN" "$QL_RESET"
  printf '  4. If it verifies, run it:\n'
  printf '     %ssh quick-lab.sh%s\n\n' "$QL_CYAN" "$QL_RESET"
  printf 'A hash alone is not strong verification: if the script and\n'
  printf 'its hash are hosted on the same server, a compromised host\n'
  printf 'lets an adversary backdoor the script and update the hash to\n'
  printf 'match. They cannot forge the signature file, though — that\n'
  printf 'requires the private key SecDim signed it with, which never\n'
  printf 'leaves SecDim.\n\n'
  printf 'Come back and run this again once you'"'"'ve checked.\n'
  exit 0
}

# ----------------------------------------------------------------
# PHASE 3 — Environment check
# ----------------------------------------------------------------

quick_lab_check_environment() {
  printf '%sChecking your environment...%s\n\n' "$QL_BOLD" "$QL_RESET"

  quick_lab_print_os_line

  if ! quick_lab_run_check "Git found: $(quick_lab_git_version_label)" "command -v git"; then
    quick_lab_git_missing
  fi

  if ! quick_lab_run_check "make found" "command -v make"; then
    quick_lab_make_missing
  fi

  if quick_lab_run_check "Docker found: $(quick_lab_docker_version_label)" "command -v docker && docker info"; then
    printf '\n  %s  Docker available — your lab will run in a container.\n' "$(quick_lab_mark_ok)"
    printf '     No local language runtime needed.\n\n'
    QL_RUNTIME_MODE=docker
  else
    printf '\n  Docker not found. Checking language runtimes...\n\n'
    QL_RUNTIME_MODE=runtime
  fi
}

quick_lab_print_os_line() {
  os_name=$(uname -s)
  case "$os_name" in
    Darwin) os_label="macOS $(sw_vers -productVersion 2>/dev/null || echo unknown)" ;;
    Linux) os_label="Linux $(uname -r)" ;;
    *) os_label="$os_name" ;;
  esac
  printf '  %s  OS detected: %s\n' "$(quick_lab_mark_ok)" "$os_label"
}

quick_lab_git_version_label() {
  if command -v git >/dev/null 2>&1; then
    git --version | awk '{print $1, $3}'
  else
    printf 'not found'
  fi
}

quick_lab_docker_version_label() {
  if command -v docker >/dev/null 2>&1; then
    docker --version | sed 's/,.*//'
  else
    printf 'not found'
  fi
}

quick_lab_git_missing() {
  printf '\n  %s  Git not found.\n\n' "$(quick_lab_mark_bad)"
  printf 'Git is required to clone the lab.\n'
  printf 'Install it from: %shttps://git-scm.com/downloads%s\n' "$QL_CYAN" "$QL_RESET"
  printf 'Then run this script again.\n\n'
  printf 'Or jump straight into the browser, sign up and use\n'
  printf 'SecDim secure browser sandboxes. No install needed:\n'
  printf '%splay.secdim.com%s\n\n' "$QL_CYAN" "$QL_RESET"
  printf 'Exiting.\n'
  exit 1
}

quick_lab_make_missing() {
  printf '\n  %s  make not found.\n\n' "$(quick_lab_mark_bad)"
  printf 'Labs are driven by a Makefile (make run, make test, ...).\n'
  printf 'Install it:\n\n'
  printf '  macOS:          xcode-select --install\n'
  printf '  Debian/Ubuntu:  sudo apt install make\n'
  printf '  Alpine:         sudo apk add make\n\n'
  printf 'Then run this script again.\n'
  printf 'Exiting.\n'
  exit 1
}

# ----------------------------------------------------------------
# PHASE 4B — Runtime selection (only reached when Docker is absent)
# ----------------------------------------------------------------

# label|repo-slug|file-ext|detect — python's repo slug and file
# extension differ, everyone else uses the same string for both.
QL_RUNTIMES='
Python|python|py|command -v python3 >/dev/null 2>&1 || command -v python >/dev/null 2>&1
Node.js|js|js|command -v node >/dev/null 2>&1
Go|go|go|command -v go >/dev/null 2>&1
Java|java|java|command -v java >/dev/null 2>&1
Ruby|ruby|ruby|command -v ruby >/dev/null 2>&1
C / gcc|c|c|command -v gcc >/dev/null 2>&1 || command -v cc >/dev/null 2>&1
C#|cs|cs|command -v dotnet >/dev/null 2>&1 || command -v csc >/dev/null 2>&1
Solidity|sol|sol|command -v solc >/dev/null 2>&1
PHP|php|php|command -v php >/dev/null 2>&1
'

quick_lab_select_runtime() {
  found_labels=""
  found_slugs=""
  found_exts=""
  found_count=0

  old_ifs=$IFS
  IFS='
'
  for entry in $QL_RUNTIMES; do
    [ -n "$entry" ] || continue
    IFS='|' read -r label slug ext detect <<EOF
$entry
EOF
    if quick_lab_run_check "$label" "$detect"; then
      found_count=$((found_count + 1))
      if [ -z "$found_labels" ]; then
        found_labels="$label"
        found_slugs="$slug"
        found_exts="$ext"
      else
        found_labels="$found_labels
$label"
        found_slugs="$found_slugs
$slug"
        found_exts="$found_exts
$ext"
      fi
    fi
  done
  IFS=$old_ifs

  if [ "$found_count" -eq 0 ]; then
    printf '\n  %s  No supported runtime found.\n\n' "$(quick_lab_mark_bad)"
    printf 'Install Docker (recommended) or one of:\n'
    printf 'Python, Node.js, Go, Java, Ruby, C\n\n'
    printf 'Then run the script again.\n\n'
    printf 'Or use the browser sandbox:\n'
    printf '%splay.secdim.com%s\n\n' "$QL_CYAN" "$QL_RESET"
    printf 'Exiting.\n'
    exit 2
  fi

  if [ "$found_count" -eq 1 ]; then
    QL_SELECTED_LABEL="$found_labels"
    QL_SELECTED_SLUG="$found_slugs"
    QL_SELECTED_EXT="$found_exts"
    printf '\n  %s  One runtime found: %s\n' "$(quick_lab_mark_ok)" "$QL_SELECTED_LABEL"
    printf '  %s Using %s for your lab.\n\n' "$(quick_lab_mark_arrow)" "$QL_SELECTED_LABEL"
    return
  fi

  printf '\n%sWhich language do you want to use?%s\n\n' "$QL_BOLD" "$QL_RESET"
  index=1
  old_ifs=$IFS
  IFS='
'
  for label in $found_labels; do
    printf '  [%d] %s\n' "$index" "$label"
    index=$((index + 1))
  done
  IFS=$old_ifs
  printf '\n%sEnter number:%s ' "$QL_BOLD" "$QL_RESET"

  choice=$(quick_lab_read_line)
  case "$choice" in
    ''|*[!0-9]*)
      printf 'Not a number. Using the first option.\n'
      choice=1
      ;;
  esac
  if [ "$choice" -lt 1 ] || [ "$choice" -gt "$found_count" ]; then
    choice=1
  fi

  QL_SELECTED_LABEL=$(printf '%s\n' "$found_labels" | sed -n "${choice}p")
  QL_SELECTED_SLUG=$(printf '%s\n' "$found_slugs" | sed -n "${choice}p")
  QL_SELECTED_EXT=$(printf '%s\n' "$found_exts" | sed -n "${choice}p")
  printf '\n'
}

quick_lab_runtime_mode_notice() {
  printf '  %s  No Docker — install the lab'"'"'s own dependencies\n' "$(quick_lab_mark_ok)"
  printf '     yourself once cloned (see Readme.adoc for the list).\n\n'
}

# ----------------------------------------------------------------
# PHASE 5 — Clone
# ----------------------------------------------------------------

quick_lab_clone() {
  if [ "$QL_RUNTIME_MODE" = docker ]; then
    QL_SELECTED_SLUG=python
    QL_SELECTED_EXT=py
    QL_SELECTED_LABEL=Python
  fi

  repo_url="${QL_LAB_HOST}/${QL_SELECTED_SLUG}/${QL_LAB_SLUG}.${QL_SELECTED_EXT}.git"
  target_dir="${QL_LAB_SLUG}.${QL_SELECTED_EXT}"

  printf '%sCloning your lab...%s\n\n' "$QL_BOLD" "$QL_RESET"
  printf '  %s$%s %s\n\n' "$QL_DIM" "$QL_RESET" "$repo_url"

  if [ -e "$target_dir" ]; then
    printf '  %s  %s already exists here — skipping clone.\n\n' "$(quick_lab_mark_bad)" "$target_dir"
  else
    quick_lab_clone_with_progress "$repo_url" "$target_dir" || {
      printf '\nCheck your network connection and try again.\n'
      exit 3
    }
  fi

  printf '  %s  Cloned to ./%s\n\n' "$(quick_lab_mark_ok)" "$target_dir"
  QL_TARGET_DIR="$target_dir"
}

quick_lab_clone_with_progress() {
  repo_url="$1"
  target_dir="$2"
  log_file="${TMPDIR:-/tmp}/quick-lab-clone.$$"
  : > "$log_file"

  if [ ! -t 1 ] || [ "${TERM:-}" = "dumb" ]; then
    if git clone --quiet "$repo_url" "$target_dir" 2>"$log_file"; then
      rm -f "$log_file"
      return 0
    fi
    printf '  %s  Could not clone the lab.\n\n' "$(quick_lab_mark_bad)"
    cat "$log_file" 2>/dev/null || true
    rm -f "$log_file"
    return 1
  fi

  git clone --progress "$repo_url" "$target_dir" >"$log_file" 2>&1 &
  clone_pid=$!

  printf '\033[?25l'
  quick_lab_animate_clone_progress "$log_file" &
  progress_pid=$!

  if wait "$clone_pid"; then
    clone_status=0
  else
    clone_status=$?
  fi

  kill "$progress_pid" 2>/dev/null || true
  wait "$progress_pid" 2>/dev/null || true
  printf '\r\033[K\033[?25h'

  if [ "$clone_status" -ne 0 ]; then
    printf '  %s  Could not clone the lab.\n\n' "$(quick_lab_mark_bad)"
    tr '\r' '\n' < "$log_file" | awk 'NF'
    rm -f "$log_file"
    return 1
  fi

  rm -f "$log_file"
  return 0
}

quick_lab_animate_clone_progress() {
  log_file="$1"
  step=0
  label="starting clone"
  while :; do
    label=$(quick_lab_clone_progress_label "$log_file" "$label")
    percent=$(printf '%s' "$label" | sed -n 's/.*[^0-9]\([0-9]\{1,3\}\)%.*/\1/p' | tail -1)
    case "$percent" in
      ''|*[!0-9]*) percent=0 ;;
    esac
    [ "$percent" -gt 100 ] 2>/dev/null && percent=100
    quick_lab_draw_clone_progress "$step" "$percent" "$label"
    step=$((step + 1))
    quick_lab_sleep 0.08
  done
}

# git writes progress as \r-updates in place; take the last chunk as
# the current status.
quick_lab_clone_progress_label() {
  log_file="$1"
  fallback="$2"
  last=$(tr '\r' '\n' < "$log_file" 2>/dev/null | awk 'NF{last=$0} END{print last}')
  case "$last" in
    "") printf '%s' "$fallback" ;;
    remote:\ Enumerating*) printf 'remote: enumerating objects' ;;
    Cloning\ into*) printf 'cloning into %s' "$target_dir" ;;
    *) printf '%s' "$last" ;;
  esac
}

quick_lab_draw_clone_progress() {
  step="$1"
  percent="$2"
  label="$3"

  if quick_lab_supports_unicode; then
    full="█"
    empty="░"
  else
    full="#"
    empty="-"
  fi

  width=24
  filled=$((percent * width / 100))
  [ "$filled" -gt "$width" ] && filled=$width

  bar=""
  i=0
  while [ "$i" -lt "$width" ]; do
    if [ "$i" -lt "$filled" ]; then
      bar="${bar}${full}"
    else
      bar="${bar}${empty}"
    fi
    i=$((i + 1))
  done

  label=$(printf '%s' "$label" | cut -c1-48)

  printf '\r\033[K  %s  %s%s%s %3d%%  %s' \
    "$(quick_lab_spinner_frame "$step")" "$QL_CYAN" "$bar" "$QL_RESET" "$percent" "$label"
}

# ----------------------------------------------------------------
# PHASE 6 — Get started
# ----------------------------------------------------------------

quick_lab_get_started() {
  # fixed 51-col interior so the borders line up regardless of how
  # long the cloned directory name is
  printf '  ┌───────────────────────────────────────────────────┐\n'
  printf '  │%51s│\n' ""
  printf '  │%-51s│\n' "   Your lab is ready."
  printf '  │%51s│\n' ""
  printf '  │%-51s│\n' "   Open Readme.adoc in your editor and follow"
  printf '  │%-51s│\n' "   the instructions inside."
  printf '  │%51s│\n' ""
  printf '  │%-51s│\n' "   cd ./$QL_TARGET_DIR"
  printf '  │%-51s│\n' "   read Readme.adoc"
  printf '  │%51s│\n' ""
  printf '  └───────────────────────────────────────────────────┘\n\n'
}

# ----------------------------------------------------------------
# PHASE 7 — Sign off
# ----------------------------------------------------------------

quick_lab_sign_off() {
  arrow=$(quick_lab_mark_arrow)
  printf 'This is one lab, one vulnerability.\n\n'
  printf 'When you'"'"'re ready for more:\n\n'
  printf '  Wargames   %s  %splay.secdim.com%s\n' "$arrow" "$QL_CYAN" "$QL_RESET"
  printf '  Courses    %s  %slearn.secdim.com%s\n\n' "$arrow" "$QL_CYAN" "$QL_RESET"
  printf 'Happy patching.\n'
}

quick_lab_main "$@"

# I cannot believe you are reading this. go patch something.
