#!/usr/bin/env bash
#
# DHIS2 AI Kit installer bootstrap
#
# This file: dhis2-kit/setup/installer.sh
#
# Remote install:
#
#   curl -fsSL \
#     https://raw.githubusercontent.com/p3ild/dak/main/dhis2-kit/setup/installer.sh \
#     | bash
#
# Dry run:
#
#   curl -fsSL \
#     https://raw.githubusercontent.com/p3ild/dak/main/dhis2-kit/setup/installer.sh \
#     | bash -s -- --dry-run
#
# Install a specific tag, branch, or commit:
#
#   curl -fsSL \
#     https://raw.githubusercontent.com/p3ild/dak/v1.0.0/dhis2-kit/setup/installer.sh \
#     | DHIS2_AI_KIT_REF=v1.0.0 bash
#
# Local usage:
#
#   bash dhis2-kit/setup/installer.sh
#   bash dhis2-kit/setup/installer.sh --dry-run
#   bash dhis2-kit/setup/installer.sh --target /path/to/project
#
# Requirements:
#   - Node.js >= 18
#   - curl, tar, and find for remote installation
#
# This Bash wrapper only:
#
#   1. Checks Node.js.
#   2. Downloads the GitHub repository when running remotely.
#   3. Locates install.mjs.
#   4. Runs the Node installer.
#
# All installation logic and source validation belong to install.mjs.

set -euo pipefail

readonly REPO_OWNER="p3ild"
readonly REPO_NAME="dak"
readonly DEFAULT_REF="main"

REF="${DHIS2_AI_KIT_REF:-$DEFAULT_REF}"
CURRENT_DIR="$(pwd)"

log() {
  # Logs use stderr because some functions return values through stdout.
  printf 'dhis2-ai-kit: %s\n' "$*" >&2
}

fail() {
  printf 'dhis2-ai-kit: error: %s\n' "$*" >&2
  exit 1
}

require_command() {
  local command_name="$1"

  if ! command -v "$command_name" >/dev/null 2>&1; then
    fail "required command not found: $command_name"
  fi
}

check_node() {
  require_command node

  local node_major

  node_major="$(
    node -p "process.versions.node.split('.')[0]"
  )"

  case "$node_major" in
    ''|*[!0-9]*)
      fail "could not determine the installed Node.js version"
      ;;
  esac

  if [ "$node_major" -lt 18 ]; then
    fail "Node.js 18 or newer is required; current version: $(node --version)"
  fi
}

resolve_script_directory() {
  local script_source="${BASH_SOURCE[0]:-}"

  # BASH_SOURCE is normally empty when invoked through:
  #
  #   curl ... | bash
  if [ -z "$script_source" ]; then
    return 1
  fi

  local script_directory

  script_directory="$(
    cd "$(dirname "$script_source")" 2>/dev/null &&
      pwd
  )" || return 1

  printf '%s\n' "$script_directory"
}

find_installer() {
  local repository_root="$1"

  local candidate

  candidate="$repository_root/dhis2-kit/setup/install.mjs"

  if [ -f "$candidate" ]; then
    printf '%s\n' "$candidate"
    return 0
  fi

  candidate="$repository_root/bin/install.mjs"

  if [ -f "$candidate" ]; then
    printf '%s\n' "$candidate"
    return 0
  fi

  candidate="$repository_root/install.mjs"

  if [ -f "$candidate" ]; then
    printf '%s\n' "$candidate"
    return 0
  fi

  candidate="$(
    find "$repository_root" \
      -mindepth 1 \
      -maxdepth 3 \
      -type f \
      -name 'install.mjs' \
      -print \
      -quit 2>/dev/null
  )"

  if [ -n "$candidate" ]; then
    printf '%s\n' "$candidate"
    return 0
  fi

  return 1
}

has_target_argument() {
  local argument

  for argument in "$@"; do
    case "$argument" in
      --target|--target=*)
        return 0
        ;;
    esac
  done

  return 1
}

reject_remote_link_mode() {
  local previous_argument=""
  local argument

  for argument in "$@"; do
    case "$argument" in
      --link|--mode=link)
        fail "link mode cannot be used for remote installation; use copy mode"
        ;;
    esac

    if (
      [ "$previous_argument" = "--mode" ] &&
      [ "$argument" = "link" ]
    ); then
      fail "link mode cannot be used for remote installation; use copy mode"
    fi

    previous_argument="$argument"
  done
}

create_temp_directory() {
  local temp_directory

  temp_directory="$(mktemp -d 2>/dev/null)" ||
    temp_directory="$(mktemp -d -t dhis2-ai-kit 2>/dev/null)" ||
    fail "could not create a temporary directory"

  printf '%s\n' "$temp_directory"
}

download_repository() {
  local temp_directory="$1"

  local archive_path="$temp_directory/repository.tar.gz"
  local extraction_directory="$temp_directory/source"
  local archive_url
  local repository_root

  mkdir -p "$extraction_directory"

  archive_url="$(
    printf \
      'https://codeload.github.com/%s/%s/tar.gz/%s' \
      "$REPO_OWNER" \
      "$REPO_NAME" \
      "$REF"
  )"

  log "downloading ${REPO_OWNER}/${REPO_NAME}@${REF}"

  curl \
    --fail \
    --silent \
    --show-error \
    --location \
    --retry 3 \
    --retry-delay 1 \
    --connect-timeout 20 \
    "$archive_url" \
    --output "$archive_path"

  if [ ! -s "$archive_path" ]; then
    fail "GitHub returned an empty repository archive"
  fi

  if ! tar -tzf "$archive_path" >/dev/null 2>&1; then
    fail "the downloaded file is not a valid gzip tar archive"
  fi

  tar \
    -xzf "$archive_path" \
    -C "$extraction_directory"

  repository_root="$(
    find "$extraction_directory" \
      -mindepth 1 \
      -maxdepth 1 \
      -type d \
      -print \
      -quit
  )"

  if [ -z "$repository_root" ]; then
    fail "could not locate the extracted repository directory"
  fi

  printf '%s\n' "$repository_root"
}

print_repository_files() {
  local repository_root="$1"

  log "repository files near root:"

  find "$repository_root" \
    -mindepth 1 \
    -maxdepth 3 \
    -type f \
    -print \
    2>/dev/null \
    | sed "s#^${repository_root}/#  #" \
    | head -100 \
    >&2
}

run_installer() {
  local installer_path="$1"
  local repository_root="$2"

  shift 2

  log "repository source: $repository_root"
  log "installer: $installer_path"

  # Run from the downloaded repository root because install.mjs may use cwd
  # as one of its source discovery strategies.
  #
  # Avoid storing "$@" in a local array. Empty arrays combined with set -u
  # are problematic on macOS Bash 3.2.
  if has_target_argument "$@"; then
    (
      cd "$repository_root"

      node \
        "$installer_path" \
        "$@"
    )
  else
    (
      cd "$repository_root"

      node \
        "$installer_path" \
        --target "$CURRENT_DIR" \
        "$@"
    )
  fi
}

run_local_if_available() {
  local script_directory
  local installer_path

  script_directory="$(resolve_script_directory || true)"

  if [ -z "$script_directory" ]; then
    return 1
  fi

  installer_path="$(find_installer "$script_directory" || true)"

  if [ -z "$installer_path" ]; then
    return 1
  fi

  log "using local repository"

  run_installer \
    "$installer_path" \
    "$script_directory" \
    "$@"

  return 0
}

run_remote() {
  require_command curl
  require_command tar
  require_command find
  require_command sed
  require_command head

  reject_remote_link_mode "$@"

  local temp_directory
  local repository_root
  local installer_path

  temp_directory="$(create_temp_directory)"

  cleanup() {
    rm -rf "$temp_directory"
  }

  trap cleanup EXIT INT TERM HUP

  repository_root="$(download_repository "$temp_directory")"

  installer_path="$(find_installer "$repository_root" || true)"

  if [ -z "$installer_path" ]; then
    print_repository_files "$repository_root"
    fail "install.mjs was not found in the downloaded repository"
  fi

  run_installer \
    "$installer_path" \
    "$repository_root" \
    "$@"
}

main() {
  check_node

  if run_local_if_available "$@"; then
    return 0
  fi

  run_remote "$@"
}

main "$@"
