#!/usr/bin/env bash
# Shared CI bootstrap for ci-fast.sh and ci-local.sh (issue #436).
set -euo pipefail

_CI_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
_CI_REPO_ROOT="$(cd "$_CI_SCRIPT_DIR/.." && pwd)"

# shellcheck source=ci-defaults.env
source "$_CI_SCRIPT_DIR/ci-defaults.env"
# shellcheck source=php-env.sh
source "$_CI_SCRIPT_DIR/php-env.sh"
# shellcheck source=ci-resource-limits.sh
source "$_CI_SCRIPT_DIR/ci-resource-limits.sh"

ci_prepare_test_runtime() {
  ci_guard_parallel_ci
  ci_apply_resource_limits
  ci_apply_default_memory_env
  ci_export_llvm_env
  export PHP_COMPILER_VM_RUNNER="${PHP_COMPILER_VM_RUNNER:-${_CI_REPO_ROOT}/script/run-vm-guarded.sh}"
}

ci_repo_root() {
  printf '%s\n' "$_CI_REPO_ROOT"
}

ci_cd_repo() {
  cd "$_CI_REPO_ROOT"
}

ci_install_deps() {
  local ext_dir="$PHP_COMPILER_EXT_DIR"
  if command -v composer >/dev/null 2>&1 && composer --version >/dev/null 2>&1; then
    COMPOSER=(composer)
  elif [[ -f /tmp/composer.phar ]]; then
    COMPOSER=("$PHP_BIN" -d "extension=$ext_dir/phar.so" -d "extension=$ext_dir/mbstring.so" /tmp/composer.phar)
  else
    python3 -c "import urllib.request; urllib.request.urlretrieve('https://getcomposer.org/download/latest-stable/composer.phar','/tmp/composer.phar')"
    COMPOSER=("$PHP_BIN" -d "extension=$ext_dir/phar.so" -d "extension=$ext_dir/mbstring.so" /tmp/composer.phar)
  fi
  "${COMPOSER[@]}" install --no-interaction --ignore-platform-reqs 2>/dev/null || true

  chmod +x script/install-llvm9.sh script/apply-patches.sh 2>/dev/null || true
  if [[ -z "${PHP_COMPILER_LLVM_PATH:-}" || ! -f "${PHP_COMPILER_LLVM_PATH}/libLLVM-9.so.1" ]]; then
    if [[ -x script/install-llvm9.sh ]]; then
      script/install-llvm9.sh || true
    fi
  fi
  if [[ -x script/apply-patches.sh ]]; then
    script/apply-patches.sh || true
  fi
}

# Regenerate committed generator output when stale (issue #765; same ergonomics as capability-matrix write).
ci_ensure_generated_doc() {
  local script="$1"
  local label="$2"
  if "$PHP_BIN" "${PHP_OPTS[@]}" "$script" --check; then
    return 0
  fi
  echo "Regenerating stale ${label} (issue #765)..."
  "$PHP_BIN" "${PHP_OPTS[@]}" "$script"
}

ci_run_capability_syntax_check() {
  if [[ "${CAPABILITY_SYNTAX_CHECK:-1}" != "1" ]]; then
    echo "capability-syntax: skipped (CAPABILITY_SYNTAX_CHECK=${CAPABILITY_SYNTAX_CHECK:-0}, issue #803)"
    return 0
  fi
  echo "capability-syntax: stale check (CAPABILITY_SYNTAX_CHECK=1 default, issue #803)..."
  "$PHP_BIN" "${PHP_OPTS[@]}" script/capability-syntax.php --check
}

ci_run_inventory_checks() {
  script/check-no-unlimited-memory.sh
  script/check-stale-issue-refs.sh
  script/check-init-miniwebapp-parity.sh
  "$PHP_BIN" "${PHP_OPTS[@]}" script/capability-matrix.php --check
  ci_run_capability_syntax_check
  ci_ensure_generated_doc script/bootstrap-inventory.php docs/bootstrap-inventory.md
  ci_ensure_generated_doc script/bootstrap-profile.php docs/bootstrap-profile.json
}

ci_llvm_dir() {
  LLVM_DIR="${PHP_COMPILER_LLVM_PATH:-$_CI_REPO_ROOT/.llvm}"
  if [[ -f "$LLVM_DIR/libLLVM-9.so.1" ]]; then
    LLVM_DIR="$(cd "$LLVM_DIR" && pwd)"
  fi
  printf '%s\n' "$LLVM_DIR"
}

# Absolute LLVM paths for PHPUnit and bin/jit.php / bin/vm.php children (#98).
ci_export_llvm_env() {
  local llvm_dir
  llvm_dir="$(ci_llvm_dir)"
  if [[ ! -f "$llvm_dir/libLLVM-9.so.1" ]]; then
    return 0
  fi
  export PHP_COMPILER_LLVM_PATH="$llvm_dir"
  export LD_LIBRARY_PATH="${llvm_dir}${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}"
  export PATH="${llvm_dir}:${PATH}"
}

ci_run_phpunit() {
  ci_export_llvm_env
  "$PHP_BIN" "${PHP_OPTS[@]}" vendor/bin/phpunit "$@"
}

ci_report_llvm_status() {
  local llvm_dir
  llvm_dir="$(ci_llvm_dir)"
  if [[ -f "$llvm_dir/libLLVM-9.so.1" ]]; then
    echo "LLVM 9 found at $llvm_dir: JIT compliance, AOT fixtures (simple_web_*, static_web), and ExampleWebAotTest will run."
  else
    echo "LLVM 9 missing: @group llvm tests (JIT, AOT, web AOT) are skipped. Run: script/install-llvm9.sh"
  fi
}

# Optional early JIT bootstrap check for ci-fast (issue #728; default off).
ci_jit_preflight_gate() {
  if [[ "${JIT_PREFLIGHT_GATE:-}" != "1" ]]; then
    return 0
  fi
  echo "JIT preflight gate (JIT_PREFLIGHT_GATE=1, issue #728)..."
  "$PHP_BIN" "${PHP_OPTS[@]}" script/check-jit-compliance-ran.php --preflight "$_CI_REPO_ROOT"
}

ci_can_bind_loopback() {
  "$PHP_BIN" "${PHP_OPTS[@]}" script/can-bind-loopback.php
}

ci_configure_serve_tests() {
  if [[ -n "${PHP_COMPILER_SKIP_SERVE_TESTS:-}" ]]; then
    echo "HTTP serve integration tests skipped (PHP_COMPILER_SKIP_SERVE_TESTS is set)."
    return
  fi
  if [[ "${PHP_COMPILER_RUN_SERVE_TESTS:-}" == "1" ]]; then
    echo "HTTP serve integration tests forced (PHP_COMPILER_RUN_SERVE_TESTS=1)."
    return
  fi
  if ci_can_bind_loopback; then
    echo "Loopback TCP bind OK: ServeTest and ServeAotTest will run."
    return
  fi
  export PHP_COMPILER_SKIP_SERVE_TESTS=1
  echo "Cannot bind 127.0.0.1 — skipping @group serve tests."
  echo "  Set PHP_COMPILER_RUN_SERVE_TESTS=1 to force, or PHP_COMPILER_SKIP_SERVE_TESTS=1 to silence."
}

ci_llvm_ready() {
  local llvm_dir
  llvm_dir="$(ci_llvm_dir)"
  [[ -f "$llvm_dir/libLLVM-9.so.1" ]]
}

ci_run_bootstrap_aot_lint() {
  echo "Bootstrap AOT lint (issue #212 Phase B)..."
  set +e
  "$PHP_BIN" "${PHP_OPTS[@]}" script/bootstrap-aot-lint.php
  local bootstrap_lint_code=$?
  set -e
  if [[ "$bootstrap_lint_code" -eq 0 ]]; then
    :
  elif [[ "$bootstrap_lint_code" -eq 2 ]]; then
    echo "bootstrap-aot-lint skipped (LLVM 9 not available)."
  else
    exit 1
  fi
}

# compiler_minimal -l/-o probe (issue #816, #829); default on in ci-local llvm tail only.
ci_run_bootstrap_selfhost_probe() {
  if [[ "${BOOTSTRAP_SELFHOST_PROBE_GATE:-0}" != "1" ]]; then
    return 0
  fi
  if ! ci_llvm_ready; then
    echo "bootstrap-selfhost-probe: skipped (LLVM 9 not available)"
    return 0
  fi
  echo "bootstrap-selfhost-probe (BOOTSTRAP_SELFHOST_PROBE_GATE=1, issue #829)..."
  local -a probe_args=()
  if [[ "${BOOTSTRAP_SELFHOST_PROBE_UPDATE:-0}" == "1" ]]; then
    probe_args+=(--update-inventory)
  fi
  "$_CI_SCRIPT_DIR/bootstrap-selfhost-compile-probe.sh" "${probe_args[@]}"
}

# Wave gate: selfhost-lint → aot-lint → probe (default on when LLVM ready; BOOTSTRAP_WAVE_CHECK=0 to skip).
ci_run_bootstrap_wave_check() {
  if [[ "${BOOTSTRAP_WAVE_CHECK:-1}" != "1" ]]; then
    return 0
  fi
  if ! ci_llvm_ready; then
    echo "bootstrap-wave-check: skipped (LLVM 9 not available)"
    return 0
  fi
  echo "bootstrap-wave-check (BOOTSTRAP_WAVE_CHECK=1)..."
  "$_CI_SCRIPT_DIR/bootstrap-wave-check.sh" --fail-fast
}

ci_should_run_jit() {
  if [[ -n "${PHP_COMPILER_FORCE_JIT_TESTS:-}" ]]; then
    echo "JIT compliance forced (PHP_COMPILER_FORCE_JIT_TESTS=1)."
    return 0
  fi
  if "$PHP_BIN" "${PHP_OPTS[@]}" script/jit-runtime-probe.php; then
    return 0
  fi
  echo "JIT MCJIT probe failed (segfault or bad output); skipping @group jit."
  echo "  Re-run with PHP_COMPILER_FORCE_JIT_TESTS=1 after fixing bin/jit.php / LLVM 9."
  return 1
}

ci_guard_jit_compliance() {
  local junit_path="$1"
  local llvm_dir="$2"
  if [[ -n "${PHP_COMPILER_ALLOW_JIT_SKIP:-}" ]]; then
    echo "JIT compliance guard skipped (PHP_COMPILER_ALLOW_JIT_SKIP is set)."
    return 0
  fi
  "$PHP_BIN" "${PHP_OPTS[@]}" script/check-jit-compliance-ran.php "$junit_path" "$llvm_dir"
}

# Shell curl harness for 003-MiniWebApp PATH_INFO routes (issue #633).
ci_run_miniwebapp_web_smoke() {
  if [[ -n "${PHP_COMPILER_SKIP_SERVE_TESTS:-}" ]]; then
    echo "examples-web-smoke (003): skipped (PHP_COMPILER_SKIP_SERVE_TESTS is set)"
    return 0
  fi
  if ! ci_can_bind_loopback; then
    echo "examples-web-smoke (003): skipped (cannot bind loopback TCP)"
    return 0
  fi
  echo "examples-web-smoke (003): MiniWebApp PATH_INFO curls (MINIWEBAPP_WEB_SMOKE_GATE=1 default, #633, #664)..."
  "$_CI_SCRIPT_DIR/examples-web-smoke.sh" --miniwebapp-only
}

# HTTP curl harness for shipped web examples via phpc serve --aot (issue #444).
ci_run_examples_web_smoke_aot() {
  if [[ -n "${PHP_COMPILER_SKIP_SERVE_TESTS:-}" ]]; then
    echo "examples-web-smoke (AOT): skipped (PHP_COMPILER_SKIP_SERVE_TESTS is set)"
    return 0
  fi
  if ! ci_can_bind_loopback; then
    echo "examples-web-smoke (AOT): skipped (cannot bind loopback TCP)"
    return 0
  fi
  echo "examples-web-smoke-prebuild: building shipped web example AOT binaries..."
  "$_CI_SCRIPT_DIR/examples-web-smoke-prebuild.sh"
  echo "examples-web-smoke (AOT): HTTP harness via phpc serve --aot..."
  "$_CI_SCRIPT_DIR/examples-web-smoke.sh" --aot
}

# CLI AOT build + execute smoke (issue #667); default on via EXAMPLES_AOT_SMOKE_GATE=1 (#674).
ci_run_examples_aot_smoke() {
  if [[ "${EXAMPLES_AOT_SMOKE_GATE:-1}" != "1" ]]; then
    return 0
  fi
  echo "examples-aot-smoke: CLI build + execute (EXAMPLES_AOT_SMOKE_GATE=1 default, #667, #674)..."
  "$_CI_SCRIPT_DIR/examples-aot-smoke.sh"
}

# phpc deploy + PHPC_DEPLOY_ROOT CGI smoke for 001/002 (issue #718); default on via DEPLOY_SMOKE_GATE=1 (#737).
ci_run_deploy_smoke() {
  if [[ "${DEPLOY_SMOKE_GATE:-1}" != "1" ]]; then
    return 0
  fi
  if ! ci_llvm_ready; then
    echo "deploy-smoke: skipped (LLVM 9 not available)"
    return 0
  fi
  echo "deploy-smoke: phpc deploy + PHPC_DEPLOY_ROOT (DEPLOY_SMOKE_GATE=1 default, #718, #737)..."
  "$_CI_SCRIPT_DIR/deploy-smoke.sh" --example 001
  "$_CI_SCRIPT_DIR/deploy-smoke.sh" --example 002
  if [[ "${DEPLOY_SMOKE_003_EXECUTE:-0}" == "1" || "${MINIWEBAPP_AOT_EXECUTE_GATE:-0}" == "1" ]]; then
    "$_CI_SCRIPT_DIR/deploy-smoke.sh" --example 003
  fi
}

# @group aot-link PHPUnit (link-only; execute is ci_run_miniwebapp_aot_execute — #775).
ci_run_aot_link_phpunit() {
  local -a aot_link_args=(--group aot-link --exclude-group serve --exclude-group miniwebapp-aot-execute)
  echo "PHPUnit: AOT link (@group aot-link)..."
  ci_run_phpunit "${aot_link_args[@]}" "$@"
}

# 003-MiniWebApp AOT binary CLI execute (issues #747, #775); default on via MINIWEBAPP_AOT_EXECUTE_GATE=1.
ci_run_miniwebapp_aot_execute() {
  if [[ "${MINIWEBAPP_AOT_EXECUTE_GATE:-1}" != "1" ]]; then
    return 0
  fi
  if ! ci_llvm_ready; then
    echo "PHPUnit: MiniWebApp AOT execute skipped (LLVM 9 not available)"
    return 0
  fi
  echo "PHPUnit: MiniWebApp AOT execute (@group miniwebapp-aot-execute; MINIWEBAPP_AOT_EXECUTE_GATE=1, #747, #775)..."
  ci_run_phpunit --group miniwebapp-aot-execute "$@"
}
