#!/usr/bin/env bash
set -euo pipefail

repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
cd "${repo_root}"

dist_index="web/dist/index.html"
fingerprint_file="web/dist/.agentteam-e2e-source-fingerprint"

sha256_stream() {
  if command -v sha256sum >/dev/null 2>&1; then
    sha256sum
    return
  fi
  shasum -a 256
}

hash_file_cmd=()
if command -v sha256sum >/dev/null 2>&1; then
  hash_file_cmd=(sha256sum)
elif command -v shasum >/dev/null 2>&1; then
  hash_file_cmd=(shasum -a 256)
else
  echo "sha256 tool is required to fingerprint web sources" >&2
  exit 1
fi

list_source_paths() {
  {
    find web/src web/public docs/public -type f -print 2>/dev/null
    printf '%s\n' \
      web/components.json \
      web/index.html \
      web/package.json \
      web/package-lock.json \
      web/tsconfig.app.json \
      web/tsconfig.json \
      web/tsconfig.node.json \
      web/vite.config.ts
  } | LC_ALL=C sort
}

fingerprint_sources() {
  local source_paths=()
  local source_path=""
  while IFS= read -r source_path; do
    if [[ -f "${source_path}" ]]; then
      source_paths+=("${source_path}")
    fi
  done < <(list_source_paths)
  if (( ${#source_paths[@]} > 0 )); then
    "${hash_file_cmd[@]}" "${source_paths[@]}"
  fi | sha256_stream | awk '{print $1}'
}

newer_than_dist() {
  local source_path=""
  while IFS= read -r source_path; do
    if [[ -f "${source_path}" && "${source_path}" -nt "${dist_index}" ]]; then
      return 0
    fi
  done < <(list_source_paths)
  return 1
}

current_fingerprint="$(fingerprint_sources)"

if [[ -f "${dist_index}" ]]; then
  if [[ -f "${fingerprint_file}" ]] && [[ "$(cat "${fingerprint_file}")" == "${current_fingerprint}" ]]; then
    echo "web/dist is up to date for E2E"
    exit 0
  fi

  if [[ ! -f "${fingerprint_file}" ]] && ! newer_than_dist; then
    printf '%s\n' "${current_fingerprint}" > "${fingerprint_file}"
    echo "web/dist is up to date for E2E"
    exit 0
  fi
fi

# Shared content-addressed dist cache. `web/dist` is gitignored and lives inside
# each worktree, so without this cache every fresh worktree (or CI job) rebuilds
# the frontend from scratch even though web/node_modules is already shared.
# The cache is keyed on the source fingerprint, the identity of the installed
# node_modules entry, and the Node runtime, so lockfile/source/runtime changes
# invalidate it. Set AGENTTEAM_WEB_DIST_CACHE=0 to disable it and always build.
link_path="web/node_modules"
dist_cache_root="${XDG_CACHE_HOME:-${HOME}/.cache}/agentteam/web-dist"
use_dist_cache="1"
if [[ "${AGENTTEAM_WEB_DIST_CACHE:-1}" == "0" ]]; then
  use_dist_cache="0"
fi

node_modules_identity=""
if [[ -L "${link_path}" ]]; then
  node_modules_identity="$(basename "$(dirname "$(readlink "${link_path}")")")"
elif [[ -f "${link_path}/.agentteam-node-modules.lock-hash" ]]; then
  node_modules_identity="$(cat "${link_path}/.agentteam-node-modules.lock-hash")"
else
  use_dist_cache="0"
fi

dist_cache_key=""
dist_cache_dir=""
if [[ "${use_dist_cache}" == "1" ]]; then
  dist_cache_key="$({
    printf '%s\n' "${current_fingerprint}"
    printf '%s\n' "${node_modules_identity}"
    printf '%s\n' "$(node -v 2>/dev/null || true)"
    printf '%s\n' "e2e"
  } | sha256_stream | awk '{print $1}')"
  dist_cache_dir="${dist_cache_root}/${dist_cache_key}"
fi

if [[ "${use_dist_cache}" != "1" ]]; then
  (
    cd web
    npm run build:e2e
  )
  printf '%s\n' "$(fingerprint_sources)" > "${fingerprint_file}"
  exit 0
fi

mkdir -p "${dist_cache_root}"

# Generate a manifest/digest covering every regular file in the dist (excluding
# agentteam bookkeeping files). Deterministic: relative paths (`./...`) sorted by
# name, one `<sha256>  <path>` line per file. Both seeding and restore-time
# validation use the same generator, so a restore only trusts an entry whose
# stored manifest still matches its actual contents.
generate_dist_manifest() {
  local dir="$1"
  (
    cd "${dir}" || exit 1
    local files=()
    local rel=""
    while IFS= read -r -d '' rel; do
      files+=("${rel}")
    done < <(find . -type f \
      ! -name '.manifest' \
      ! -name '.ready' \
      ! -name '.agentteam-e2e-source-fingerprint' \
      -print0 2>/dev/null | LC_ALL=C sort -z)
    if (( ${#files[@]} > 0 )); then
      "${hash_file_cmd[@]}" "${files[@]}"
    fi
  )
}

# A cached entry is only usable when its manifest parses and regenerating the
# manifest over the current contents reproduces the stored one byte-for-byte.
# That catches a missing asset, a tampered asset, an extra file, or a corrupted
# manifest itself. Uses a single batched sha256sum for speed.
dist_manifest_valid() {
  local dir="$1"
  local manifest_file="${dir}/.manifest"
  [[ -f "${manifest_file}" ]] || return 1
  if ! awk 'NF != 2 || $1 !~ /^[0-9a-f]{64}$/ || $2 !~ /^\.\// { exit 1 }' "${manifest_file}"; then
    return 1
  fi
  [[ "$(generate_dist_manifest "${dir}")" == "$(cat "${manifest_file}")" ]]
}

dist_cache_valid() {
  [[ -d "${dist_cache_dir}" &&
     -f "${dist_cache_dir}/index.html" &&
     -f "${dist_cache_dir}/.ready" &&
     -f "${dist_cache_dir}/.agentteam-e2e-source-fingerprint" &&
     "$(cat "${dist_cache_dir}/.agentteam-e2e-source-fingerprint")" == "${current_fingerprint}" ]] &&
    dist_manifest_valid "${dist_cache_dir}"
}

restore_dist_from_cache() {
  rm -rf "web/dist"
  mkdir -p "web/dist"
  cp -a "${dist_cache_dir}/." "web/dist/"
  rm -f "web/dist/.ready" "web/dist/.manifest"
  printf '%s\n' "${current_fingerprint}" > "${fingerprint_file}"
  echo "Restored web/dist from shared cache at ${dist_cache_dir}"
}

# Build and seed under an exclusive lock so concurrent worktrees (or CI jobs)
# sharing the same inputs build once and the rest reuse the entry. Mirrors the
# ensure-web-node-modules.sh cache lock.
lock_dir="${dist_cache_dir}.lock"
lock_owner_file="${lock_dir}/owner.pid"
lock_wait_timeout_seconds="${AGENTTEAM_WEB_DIST_LOCK_WAIT_TIMEOUT_SECONDS:-600}"
ownerless_lock_grace_seconds="${AGENTTEAM_WEB_DIST_OWNERLESS_LOCK_GRACE_SECONDS:-5}"

if ! [[ "${lock_wait_timeout_seconds}" =~ ^[0-9]+$ ]] || [[ "${lock_wait_timeout_seconds}" -le 0 ]]; then
  echo "AGENTTEAM_WEB_DIST_LOCK_WAIT_TIMEOUT_SECONDS must be a positive integer" >&2
  exit 1
fi
if ! [[ "${ownerless_lock_grace_seconds}" =~ ^[0-9]+$ ]]; then
  echo "AGENTTEAM_WEB_DIST_OWNERLESS_LOCK_GRACE_SECONDS must be a non-negative integer" >&2
  exit 1
fi

lock_acquired="false"
dist_lock_cleanup() {
  if [[ "${lock_acquired}" == "true" ]]; then
    rm -rf "${lock_dir}"
  fi
}
trap dist_lock_cleanup EXIT

lock_wait_started_at="$(date +%s)"
while true; do
  if mkdir "${lock_dir}" 2>/dev/null; then
    lock_acquired="true"
    printf '%s\n' "$$" > "${lock_owner_file}"
    break
  fi
  if [[ ! -d "${lock_dir}" ]]; then
    echo "Unable to create shared web dist cache lock at ${lock_dir}" >&2
    exit 1
  fi
  lock_wait_now="$(date +%s)"
  if [[ -f "${lock_owner_file}" ]]; then
    lock_owner_pid="$(cat "${lock_owner_file}" 2>/dev/null || true)"
    if [[ "${lock_owner_pid}" =~ ^[0-9]+$ ]] && ! kill -0 "${lock_owner_pid}" 2>/dev/null; then
      echo "Removing stale shared web dist cache lock at ${lock_dir} from pid ${lock_owner_pid}"
      rm -rf "${lock_dir}"
      continue
    fi
  elif (( lock_wait_now - lock_wait_started_at >= ownerless_lock_grace_seconds )); then
    echo "Removing ownerless stale shared web dist cache lock at ${lock_dir}"
    rm -rf "${lock_dir}"
    continue
  fi
  if (( lock_wait_now - lock_wait_started_at >= lock_wait_timeout_seconds )); then
    echo "Timed out after ${lock_wait_timeout_seconds}s waiting for shared web dist cache at ${dist_cache_dir}" >&2
    exit 1
  fi
  sleep 1
done

# Another process may have seeded the cache while we waited on the lock.
if dist_cache_valid; then
  restore_dist_from_cache
  exit 0
fi

# The entry is absent, half-written, or failed integrity validation. Remove it
# so no other reader can restore a broken or incomplete dist, then rebuild.
if [[ -d "${dist_cache_dir}" ]]; then
  echo "Shared web dist cache at ${dist_cache_dir} is invalid or stale; rebuilding" >&2
  rm -rf "${dist_cache_dir}"
fi

(
  cd web
  npm run build:e2e
)
printf '%s\n' "$(fingerprint_sources)" > "${fingerprint_file}"

seed_tmp_dir="${dist_cache_dir}.tmp.$$"
rm -rf "${seed_tmp_dir}"
mkdir -p "${seed_tmp_dir}"
cp -a "web/dist/." "${seed_tmp_dir}/"
# Manifest/digest covers the complete dist so a restore can verify every asset
# before use. It is generated from the staged copy (excluding agentteam bookkeeping)
# and committed before .ready: the atomic mv below means the entry is only ever
# visible complete, and a reader that somehow observes a torn entry still fails
# the integrity check and rebuilds instead of restoring a broken dist.
generate_dist_manifest "${seed_tmp_dir}" > "${seed_tmp_dir}/.manifest"
printf '%s\n' "${current_fingerprint}" > "${seed_tmp_dir}/.agentteam-e2e-source-fingerprint"
touch "${seed_tmp_dir}/.ready"
rm -rf "${dist_cache_dir}"
mv "${seed_tmp_dir}" "${dist_cache_dir}"
echo "Seeded shared web dist cache at ${dist_cache_dir}"
