#!/usr/bin/env bash
# Detects changed packages and outputs PNPM_FILTER + deploy-gate flags.
# Called by Jenkinsfile; outputs KEY=VALUE lines to stdout.
#
# Pure bash + sed — no node dependency (the Jenkins agent runs before
# node is available inside the Docker build stages).
#
# PNPM_FILTER uses pnpm's native `--filter pkg...` (trailing ...) which
# includes the package plus all transitive dependents — no manual BFS
# needed for test/lint scope.
#
# Deploy gates BFS-propagate through reverse workspace deps (read from
# package.json via sed) to check if a gate app is transitively affected.
set -euo pipefail

BASE="${DIFF_BASE:-HEAD~1}"

# ── Dynamic package discovery ─────────────────────────────────────────
# Reads workspace packages from the filesystem — no static registry.
# New packages are discovered automatically. Pure bash + sed — no node
# dependency (the Jenkins agent may not have node on PATH).
DIRS=()
NAMES=()
for glob in apps packages; do
  for d in "$glob"/*/; do
    pj="${d}package.json"
    if [[ -f "$pj" ]]; then
      name=$(sed -n 's/.*"name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$pj" | head -1)
      if [[ -n "$name" ]]; then
        DIRS+=("$d")
        NAMES+=("$name")
      fi
    fi
  done
done

# ── Deploy-gate config ───────────────────────────────────────────────
# Which top-level apps gate each deploy flag.
SERVER_GATE_RE='^(@coda/server-app|@coda/client-app)$'
PLATFORM_GATE_RE='^@coda/platform-service$'
SEARCH_GATE_RE='^@coda/search-service$'
RUNNER_GATE_RE='^@coda/runner-service$'
LANGFUSE_GATE_RE='^@coda/langfuse$'

ALL_FILTER=""
for name in "${NAMES[@]}"; do
  ALL_FILTER="${ALL_FILTER:+$ALL_FILTER }--filter $name"
done

# ── Ensure base ref is available (shallow clones may lack it) ────────
if ! git rev-parse --verify "$BASE" >/dev/null 2>&1; then
  # Try fetching the specific ref — handles both "origin/branch" and "HEAD~N"
  if [[ "$BASE" == origin/* ]]; then
    git fetch --depth=1 origin "${BASE#origin/}" >/dev/null 2>&1 || true
  else
    git fetch --deepen=2 >/dev/null 2>&1 || true
  fi
fi

if ! git rev-parse --verify "$BASE" >/dev/null 2>&1; then
  echo "WARNING: base ref '$BASE' not resolvable after fetch; falling back to full build" >&2
  printf 'PNPM_FILTER=%s\nSERVER_CHANGED=true\nPLATFORM_CHANGED=true\nSEARCH_CHANGED=true\nRUNNER_CHANGED=true\nLANGFUSE_CHANGED=true\n' "$ALL_FILTER"
  exit 0
fi

mapfile -t FILES < <(git diff --name-only "$BASE" HEAD)
if [[ ${#FILES[@]} -eq 0 ]]; then
  printf 'PNPM_FILTER=\nSERVER_CHANGED=false\nPLATFORM_CHANGED=false\nSEARCH_CHANGED=false\nRUNNER_CHANGED=false\nLANGFUSE_CHANGED=false\n'
  exit 0
fi

# ── Infra-level changes gate deploys regardless of package graph ─────
INFRA_RE='^(Dockerfile|Jenkinsfile|docker-compose\.yml|docker-bake\.hcl|pnpm-lock\.yaml|pnpm-workspace\.yaml|package\.json)$'
INFRA_CHANGED=false
for f in "${FILES[@]}"; do
  [[ "$f" =~ $INFRA_RE ]] && { INFRA_CHANGED=true; break; }
done

# Root-level or dotfile changes → test everything
ROOT_CHANGED=false
for f in "${FILES[@]}"; do
  [[ "$f" != */* || "$f" == .* ]] && { ROOT_CHANGED=true; break; }
done

# ── Map changed files to packages ────────────────────────────────────
CHANGED=()
declare -A CHANGED_SET=()
for f in "${FILES[@]}"; do
  for i in "${!DIRS[@]}"; do
    if [[ "$f" == "${DIRS[$i]}"* && -z "${CHANGED_SET["${NAMES[$i]}"]:-}" ]]; then
      CHANGED+=("${NAMES[$i]}")
      CHANGED_SET["${NAMES[$i]}"]=1
    fi
  done
done

# ── Build PNPM_FILTER using pnpm's native ... suffix ─────────────────
# --filter pkg... = the package + all transitive dependents.
# pnpm resolves the dependency graph at runtime — no BFS in this script.
FILTER=""
for pkg in "${CHANGED[@]}"; do
  FILTER="${FILTER:+$FILTER }--filter ${pkg}..."
done

# ── Resolve affected set for deploy gates ─────────────────────────────
# BFS-propagate from the changed set through reverse workspace deps.
# Pure bash + sed — reads workspace:* deps from each package.json.

# Build reverse dependency map: dep → space-separated dependents
declare -A REV_DEPS=()
declare -A KNOWN_PKGS=()
for name in "${NAMES[@]}"; do KNOWN_PKGS["$name"]=1; done

for i in "${!DIRS[@]}"; do
  pj="${DIRS[$i]}package.json"
  while IFS= read -r dep; do
    [[ -n "${KNOWN_PKGS["$dep"]:-}" ]] && REV_DEPS["$dep"]="${REV_DEPS["$dep"]:-} ${NAMES[$i]}"
  done < <(sed -n 's/.*"\([^"]*\)"[[:space:]]*:[[:space:]]*"workspace:\*.*/\1/p' "$pj")
done

# BFS from changed packages to transitive dependents
declare -A AFFECTED_SET=()
QUEUE=()
for pkg in "${CHANGED[@]}"; do
  AFFECTED_SET["$pkg"]=1
  QUEUE+=("$pkg")
done

while [[ ${#QUEUE[@]} -gt 0 ]]; do
  current="${QUEUE[0]}"
  QUEUE=("${QUEUE[@]:1}")
  for dependent in ${REV_DEPS["$current"]:-}; do
    if [[ -z "${AFFECTED_SET["$dependent"]:-}" ]]; then
      AFFECTED_SET["$dependent"]=1
      QUEUE+=("$dependent")
    fi
  done
done

AFFECTED_LIST="${!AFFECTED_SET[*]}"

# ── Derive deploy flags from the affected set ────────────────────────
SERVER_CHANGED=$INFRA_CHANGED
PLATFORM_CHANGED=$INFRA_CHANGED
SEARCH_CHANGED=$INFRA_CHANGED
RUNNER_CHANGED=$INFRA_CHANGED
# Langfuse images are FROM langfuse/langfuse:3 with no COPY — infra changes
# (pnpm-lock.yaml, root Dockerfile) don't affect them. Only rebuild when
# apps/langfuse/ changes (e.g., bumping the Langfuse version tag).
LANGFUSE_CHANGED=false

for pkg in $AFFECTED_LIST; do
  [[ "$pkg" =~ $SERVER_GATE_RE ]] && SERVER_CHANGED=true
  [[ "$pkg" =~ $PLATFORM_GATE_RE ]] && PLATFORM_CHANGED=true
  [[ "$pkg" =~ $SEARCH_GATE_RE ]] && SEARCH_CHANGED=true
  [[ "$pkg" =~ $RUNNER_GATE_RE ]] && RUNNER_CHANGED=true
  [[ "$pkg" =~ $LANGFUSE_GATE_RE ]] && LANGFUSE_CHANGED=true
done

# Root changes → test all packages but keep graph-derived deploy flags
if [[ "$ROOT_CHANGED" == true ]]; then
  printf 'PNPM_FILTER=%s\nSERVER_CHANGED=%s\nPLATFORM_CHANGED=%s\nSEARCH_CHANGED=%s\nRUNNER_CHANGED=%s\nLANGFUSE_CHANGED=%s\n' \
    "$ALL_FILTER" "$SERVER_CHANGED" "$PLATFORM_CHANGED" "$SEARCH_CHANGED" "$RUNNER_CHANGED" "$LANGFUSE_CHANGED"
  exit 0
fi

printf 'PNPM_FILTER=%s\nSERVER_CHANGED=%s\nPLATFORM_CHANGED=%s\nSEARCH_CHANGED=%s\nRUNNER_CHANGED=%s\nLANGFUSE_CHANGED=%s\n' \
  "$FILTER" "$SERVER_CHANGED" "$PLATFORM_CHANGED" "$SEARCH_CHANGED" "$RUNNER_CHANGED" "$LANGFUSE_CHANGED"
