#!/usr/bin/env zsh
#
# Install macOS runtime dependencies for EMDS Workbench.
#
# This script is intentionally standalone. It does not source or call the older
# macOS dependency installer.
#
# It checks for:
#   1. A usable GDAL unified CLI:
#        gdal --version
#        gdal vector layer-algebra --help
#   2. Homebrew, installing it when GDAL must be installed and brew is missing.
#   3. pyarrow in the QGIS Python environment.
#
# Usage:
#   zsh scripts/macos/install-emds-runtime-deps.zsh
#
# Optional overrides:
#   ASSUME_YES=1 zsh scripts/macos/install-emds-runtime-deps.zsh
#   QGIS_APP="/Applications/QGIS.app" zsh scripts/macos/install-emds-runtime-deps.zsh
#   QGIS_PYTHON="/Applications/QGIS.app/Contents/MacOS/python3" zsh scripts/macos/install-emds-runtime-deps.zsh
#
set -euo pipefail

MIN_GDAL_MAJOR=3
MIN_GDAL_MINOR=12
PYARROW_SPEC="${PYARROW_SPEC:-pyarrow>=14.0.0}"

log() {
  print -r -- "==> $*"
}

warn() {
  print -ru2 -- "WARNING: $*"
}

fail() {
  print -ru2 -- "ERROR: $*"
  exit 1
}

confirm() {
  local prompt="$1"
  if [[ "${ASSUME_YES:-0}" == "1" ]]; then
    return 0
  fi

  print -r -- "$prompt"
  print -n -- "Continue? [y/N] "
  local answer
  read -r answer
  [[ "$answer" == [Yy] || "$answer" == [Yy][Ee][Ss] ]]
}

require_macos() {
  [[ "$(uname -s)" == "Darwin" ]] || fail "This installer is intended for macOS only."
}

find_executable() {
  local name="$1"
  local resolved=""

  resolved="$(command -v "$name" 2>/dev/null || true)"
  if [[ -n "$resolved" && -x "$resolved" ]]; then
    print -r -- "$resolved"
    return 0
  fi

  return 1
}

find_brew() {
  local candidate

  if candidate="$(find_executable brew)"; then
    print -r -- "$candidate"
    return 0
  fi

  for candidate in /opt/homebrew/bin/brew /usr/local/bin/brew; do
    if [[ -x "$candidate" ]]; then
      print -r -- "$candidate"
      return 0
    fi
  done

  return 1
}

gdal_candidates() {
  local candidate

  if candidate="$(find_executable gdal)"; then
    print -r -- "$candidate"
  fi

  for candidate in \
    /opt/homebrew/bin/gdal \
    /usr/local/bin/gdal \
    /usr/bin/gdal; do
    if [[ -x "$candidate" ]]; then
      print -r -- "$candidate"
    fi
  done
}

parse_gdal_version() {
  local gdal_bin="$1"
  local version_text

  version_text="$("$gdal_bin" --version 2>&1 || true)"
  if [[ "$version_text" =~ 'GDAL[[:space:]]+([0-9]+)\.([0-9]+)(\.([0-9]+))?' ]]; then
    print -r -- "${match[1]} ${match[2]} ${match[4]:-0}"
    return 0
  fi

  return 1
}

gdal_version_is_new_enough() {
  local gdal_bin="$1"
  local version_parts
  local major minor patch

  version_parts="$(parse_gdal_version "$gdal_bin" || true)"
  [[ -n "$version_parts" ]] || return 1

  read -r major minor patch <<< "$version_parts"
  (( major > MIN_GDAL_MAJOR )) && return 0
  (( major == MIN_GDAL_MAJOR && minor >= MIN_GDAL_MINOR )) && return 0
  return 1
}

gdal_supports_layer_algebra() {
  local gdal_bin="$1"
  "$gdal_bin" vector layer-algebra --help >/dev/null 2>&1
}

find_suitable_gdal() {
  local seen=":"
  local candidate

  for candidate in ${(f)"$(gdal_candidates)"}; do
    [[ -n "$candidate" ]] || continue
    [[ "$seen" == *":$candidate:"* ]] && continue
    seen="${seen}${candidate}:"

    if gdal_version_is_new_enough "$candidate" && gdal_supports_layer_algebra "$candidate"; then
      print -r -- "$candidate"
      return 0
    fi
  done

  return 1
}

install_homebrew() {
  confirm "Homebrew is not installed. This will run the official Homebrew installer from raw.githubusercontent.com." \
    || fail "Homebrew is required to install GDAL automatically."

  log "Installing Homebrew"
  NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
}

install_gdal_with_brew() {
  local brew_bin="$1"

  log "Installing GDAL with Homebrew"
  "$brew_bin" update
  "$brew_bin" install gdal || "$brew_bin" upgrade gdal

  if ! find_suitable_gdal >/dev/null 2>&1; then
    warn "GDAL still was not found on PATH after brew install; checking Homebrew prefix directly."
    local brew_prefix
    brew_prefix="$("$brew_bin" --prefix)"
    if [[ -x "$brew_prefix/bin/gdal" ]]; then
      print -r -- "$brew_prefix/bin/gdal"
      return 0
    fi
  fi
}

ensure_gdal() {
  local gdal_bin
  local brew_bin

  if gdal_bin="$(find_suitable_gdal)"; then
    log "GDAL CLI is ready: $gdal_bin"
    "$gdal_bin" --version
    return 0
  fi

  warn "No usable 'gdal' CLI was found. EMDS requires GDAL 3.12+ with 'gdal vector layer-algebra'."

  if ! brew_bin="$(find_brew)"; then
    install_homebrew
    brew_bin="$(find_brew)" || fail "Homebrew install finished, but brew was not found."
  fi

  log "Using Homebrew: $brew_bin"
  install_gdal_with_brew "$brew_bin"

  if gdal_bin="$(find_suitable_gdal)"; then
    log "GDAL CLI is ready: $gdal_bin"
    "$gdal_bin" --version
    return 0
  fi

  fail "GDAL was installed or updated, but 'gdal vector layer-algebra --help' still failed."
}

find_qgis_apps() {
  local search_root

  for search_root in /Applications "$HOME/Applications"; do
    [[ -d "$search_root" ]] || continue
    /usr/bin/find "$search_root" -maxdepth 1 -type d -iname 'QGIS*.app' -print 2>/dev/null
  done
}

select_qgis_app() {
  local app

  if [[ -n "${QGIS_APP:-}" ]]; then
    [[ -d "$QGIS_APP" ]] || fail "QGIS_APP does not exist: $QGIS_APP"
    print -r -- "$QGIS_APP"
    return 0
  fi

  local apps_text
  apps_text="$(find_qgis_apps | /usr/bin/sort || true)"
  [[ -n "$apps_text" ]] || fail "No QGIS*.app bundle found in /Applications or ~/Applications. Set QGIS_APP=/path/to/QGIS.app."

  app="$(print -r -- "$apps_text" | /usr/bin/tail -n 1)"
  print -r -- "$app"
}

find_qgis_python() {
  local qgis_app="$1"
  local candidate

  if [[ -n "${QGIS_PYTHON:-}" ]]; then
    [[ -x "$QGIS_PYTHON" ]] || fail "QGIS_PYTHON is not executable: $QGIS_PYTHON"
    print -r -- "$QGIS_PYTHON"
    return 0
  fi

  for candidate in \
    "$qgis_app/Contents/MacOS/python3" \
    "$qgis_app/Contents/MacOS/bin/python3" \
    "$qgis_app/Contents/Resources/python/bin/python3"; do
    if [[ -x "$candidate" ]]; then
      print -r -- "$candidate"
      return 0
    fi
  done

  candidate="$(/usr/bin/find "$qgis_app/Contents" -type f \( -name 'python3' -o -name 'python3.[0-9]*' \) ! -name '*config' -perm -111 -print 2>/dev/null | /usr/bin/sort | /usr/bin/head -n 1 || true)"
  [[ -n "$candidate" && -x "$candidate" ]] || fail "Could not find an executable QGIS Python inside: $qgis_app"
  print -r -- "$candidate"
}

repair_pythonhome_if_needed() {
  local qgis_app="$1"
  local py="$2"

  if "$py" -c 'import encodings' >/dev/null 2>&1; then
    return 0
  fi

  if [[ "${SKIP_PYTHONHOME_REPAIR:-0}" == "1" ]]; then
    warn "Skipping PYTHONHOME repair as requested (SKIP_PYTHONHOME_REPAIR=1). This may cause later failures."
    return 0
  fi

  log "QGIS Python needs PYTHONHOME when run outside QGIS; locating bundled stdlib"

  local py_version
  local encodings_init
  py_version="$("$py" --version 2>&1 | /usr/bin/awk '{print $2}' | /usr/bin/cut -d. -f1,2)"

  # Try several likely locations, including QGIS 4 Python.framework layouts
  # and the "flat" Resources/pythonX.Y layout (no intermediate lib/ segment).
  for pattern in \
      "*/python${py_version}/encodings/__init__.py" \
      "*/lib/python${py_version}/encodings/__init__.py" \
      "*/Frameworks/Python.framework/Versions/${py_version}/lib/python${py_version}/encodings/__init__.py" \
      "*/Frameworks/Python.framework/Versions/Current/lib/python${py_version}/encodings/__init__.py" \
      "*/Frameworks/Python.framework/Versions/*/lib/python${py_version}/encodings/__init__.py" \
      "*/Resources/python/lib/python${py_version}/encodings/__init__.py" \
      "*/lib/*/encodings/__init__.py"; do

    encodings_init="$(/usr/bin/find "$qgis_app/Contents" -type f -path "$pattern" -print 2>/dev/null | /usr/bin/head -n 1 || true)"
    [[ -n "$encodings_init" ]] && break
  done

  # Last-resort very broad search. NOTE: -name cannot contain a '/', so this
  # must use -path or it silently matches nothing.
  if [[ -z "$encodings_init" ]]; then
    encodings_init="$(/usr/bin/find "$qgis_app/Contents" -type f -path "*/encodings/__init__.py" -print 2>/dev/null | /usr/bin/head -n 1 || true)"
  fi

  if [[ -z "$encodings_init" ]]; then
    fail "Could not locate the QGIS Python standard library under $qgis_app/Contents.

This QGIS 4 build uses a different internal layout than expected.

Please run this command and paste the output when reporting the issue:

  find \"$qgis_app/Contents\" -path '*/encodings/__init__.py' 2>/dev/null | head -10
"
  fi

  # Directory that directly contains encodings/ (the stdlib root).
  local stdlib_dir
  stdlib_dir="$(cd "$(dirname "$encodings_init")/.." && pwd)"

  # Put the stdlib (and its C-extension dir, if present) on PYTHONPATH so a
  # standalone QGIS Python can bootstrap encodings during interpreter startup.
  # This works for both the classic <prefix>/lib/pythonX.Y layout and the
  # "flat" Resources/pythonX.Y layout used by some QGIS 4 macOS builds.
  export PYTHONPATH
  PYTHONPATH="$stdlib_dir${PYTHONPATH:+:$PYTHONPATH}"
  if [[ -d "$stdlib_dir/lib-dynload" ]]; then
    PYTHONPATH="$stdlib_dir/lib-dynload:$PYTHONPATH"
  fi
  log "Set PYTHONPATH=$PYTHONPATH"

  # Only set PYTHONHOME for the classic <prefix>/lib/pythonX.Y layout, where
  # PYTHONHOME's lib/pythonX.Y expectation actually holds. For a flat layout
  # PYTHONHOME would point Python at a nonexistent lib/ dir, so we rely on
  # PYTHONPATH alone.
  case "$stdlib_dir" in
    */lib/python*)
      export PYTHONHOME
      PYTHONHOME="$(cd "$stdlib_dir/../.." && pwd)"
      log "Set PYTHONHOME=$PYTHONHOME"
      ;;
  esac

  "$py" -c 'import encodings' >/dev/null 2>&1 || fail "QGIS Python still cannot initialize after setting PYTHONPATH/PYTHONHOME."
}

ensure_pip() {
  local py="$1"

  if "$py" -m pip --version >/dev/null 2>&1; then
    log "pip is ready: $("$py" -m pip --version)"
    return 0
  fi

  log "pip is missing from QGIS Python; bootstrapping pip into the user environment"
  if "$py" -m ensurepip --user --upgrade >/dev/null 2>&1; then
    log "pip bootstrapped with ensurepip"
    return 0
  fi

  confirm "ensurepip is unavailable. This will download get-pip.py from bootstrap.pypa.io and install pip with --user." \
    || fail "pip is required to install pyarrow into QGIS Python."

  local tmp_getpip
  tmp_getpip="$(mktemp -t emds-get-pip).py"
  trap 'rm -f "$tmp_getpip"' EXIT
  curl -fsSL https://bootstrap.pypa.io/get-pip.py -o "$tmp_getpip"
  "$py" "$tmp_getpip" --user
}

pip_supports_break_system_packages() {
  local py="$1"
  "$py" -m pip install --help 2>/dev/null | /usr/bin/grep -q -- '--break-system-packages'
}

ensure_pyarrow() {
  local py="$1"
  local -a pip_flags

  if "$py" -c 'import pyarrow' >/dev/null 2>&1; then
    log "pyarrow is already installed in QGIS Python"
    "$py" -c 'import pyarrow; print("pyarrow", pyarrow.__version__)'
    return 0
  fi

  ensure_pip "$py"

  pip_flags=(--user --no-input --disable-pip-version-check)
  if pip_supports_break_system_packages "$py"; then
    pip_flags+=(--break-system-packages)
  fi

  log "Installing ${PYARROW_SPEC} into QGIS Python user site"
  if ! "$py" -m pip install "${pip_flags[@]}" "$PYARROW_SPEC"; then
    warn "The --user install failed. Retrying inside the QGIS Python environment."
    pip_flags=(--no-input --disable-pip-version-check)
    if pip_supports_break_system_packages "$py"; then
      pip_flags+=(--break-system-packages)
    fi
    "$py" -m pip install "${pip_flags[@]}" "$PYARROW_SPEC"
  fi

  "$py" - <<'PYEOF'
import pyarrow
print("pyarrow", pyarrow.__version__, "->", pyarrow.__file__)
PYEOF
}

main() {
  require_macos

  log "Checking GDAL"
  ensure_gdal

  log "Locating QGIS Python"
  local qgis_app
  local qgis_python
  qgis_app="$(select_qgis_app)"
  qgis_python="$(find_qgis_python "$qgis_app")"
  log "Using QGIS app: $qgis_app"
  log "Using QGIS Python: $qgis_python"
  "$qgis_python" --version

  repair_pythonhome_if_needed "$qgis_app" "$qgis_python"

  log "Checking pyarrow"
  ensure_pyarrow "$qgis_python"

  print
  log "All required EMDS macOS runtime dependencies are available."
  print -r -- "Restart QGIS before running EMDS Study Area overlays."
}

main "$@"
