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

repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
web_dir="${repo_root}/web"
package_json="${web_dir}/package.json"
package_lock="${web_dir}/package-lock.json"
node_version_file="${web_dir}/.node-version"
link_path="${web_dir}/node_modules"
local_hash_marker="${link_path}/.agentteam-node-modules.lock-hash"
web_node_engine_range="^20.19.0 || >=22.12.0"

if [[ ! -f "${package_json}" || ! -f "${package_lock}" ]]; then
  echo "web/package.json or web/package-lock.json is missing" >&2
  exit 1
fi

node_version_satisfies_web_engines() {
  local version="${1#v}"
  local major minor patch
  IFS=. read -r major minor patch _ <<< "${version}"
  if ! [[ "${major}" =~ ^[0-9]+$ ]] || ! [[ "${minor:-0}" =~ ^[0-9]+$ ]]; then
    return 1
  fi
  if (( major > 22 )); then
    return 0
  fi
  if (( major == 22 && minor >= 12 )); then
    return 0
  fi
  if (( major == 20 && minor >= 19 )); then
    return 0
  fi
  return 1
}

node_bin_version() {
  local node_bin="$1"
  "${node_bin}" -v 2>/dev/null || true
}

candidate_node_bins=()
add_node_candidate() {
  local node_bin="$1"
  if [[ -z "${node_bin}" || ! -x "${node_bin}" ]]; then
    return
  fi
  local existing
  if (( ${#candidate_node_bins[@]} > 0 )); then
    for existing in "${candidate_node_bins[@]}"; do
      if [[ "${existing}" == "${node_bin}" ]]; then
        return
      fi
    done
  fi
  candidate_node_bins+=("${node_bin}")
}

select_compatible_node() {
  candidate_node_bins=()
  if [[ -n "${AGENTTEAM_WEB_NODE_BIN:-}" ]]; then
    add_node_candidate "${AGENTTEAM_WEB_NODE_BIN}"
  fi
  if command -v node >/dev/null 2>&1; then
    add_node_candidate "$(command -v node)"
  fi

  if [[ -f "${node_version_file}" ]]; then
    preferred_node_version="$(tr -d '[:space:]' < "${node_version_file}")"
    preferred_node_major="${preferred_node_version%%.*}"
    if [[ "${preferred_node_major}" =~ ^[0-9]+$ ]]; then
      add_node_candidate "/opt/homebrew/opt/node@${preferred_node_major}/bin/node"
      add_node_candidate "/usr/local/opt/node@${preferred_node_major}/bin/node"
      local runtime_dir
      for runtime_dir in \
        "${HOME}/.local/share/fnm/node-versions/v${preferred_node_major}"*/installation \
        "${HOME}/.nvm/versions/node/v${preferred_node_major}"*; do
        add_node_candidate "${runtime_dir}/bin/node"
      done
    fi
  fi

  add_node_candidate "/opt/homebrew/opt/node/bin/node"
  add_node_candidate "/usr/local/opt/node/bin/node"

  local node_bin node_version
  if (( ${#candidate_node_bins[@]} > 0 )); then
    for node_bin in "${candidate_node_bins[@]}"; do
      node_version="$(node_bin_version "${node_bin}")"
      if node_version_satisfies_web_engines "${node_version}"; then
        selected_node_bin="${node_bin}"
        selected_node_version="${node_version}"
        return 0
      fi
    done
  fi
  selected_node_bin=""
  selected_node_version=""
  return 1
}

selected_node_bin=""
selected_node_version=""
if select_compatible_node; then
  export PATH="$(dirname "${selected_node_bin}"):${PATH}"
  if ! command -v npm >/dev/null 2>&1; then
    echo "Node ${selected_node_version} at ${selected_node_bin} was selected, but npm was not found next to it or on PATH" >&2
    exit 1
  fi
  runtime_version="${selected_node_version}"
  package_manager="npm-$(npm --version)"
  install_web_dependencies() {
    npm ci
  }
  validate_installed_dependencies() {
    npm ls --depth=0 --silent >/dev/null 2>&1 &&
      node -e "import('vite')" >/dev/null 2>&1
  }
elif command -v node >/dev/null 2>&1 && command -v npm >/dev/null 2>&1; then
  current_node_bin="$(command -v node)"
  current_node_version="$(node_bin_version "${current_node_bin}")"
  echo "Node ${current_node_version:-unknown} does not satisfy web engines ${web_node_engine_range}; using npm anyway because no compatible Node was found" >&2
  runtime_version="${current_node_version:-node}"
  package_manager="npm-$(npm --version)"
  install_web_dependencies() {
    npm ci
  }
  validate_installed_dependencies() {
    npm ls --depth=0 --silent >/dev/null 2>&1 &&
      node -e "import('vite')" >/dev/null 2>&1
  }
elif command -v bun >/dev/null 2>&1; then
  runtime_version="bun-$(bun --version)"
  package_manager="${runtime_version}"
  install_web_dependencies() {
    bun install --no-save --no-progress
  }
  validate_installed_dependencies() {
    if command -v node >/dev/null 2>&1; then
      node -e "import('vite')" >/dev/null 2>&1
      return
    fi
    bun --bun -e "await import('vite')" >/dev/null 2>&1
  }
else
  current_node_version=""
  if command -v node >/dev/null 2>&1; then
    current_node_version="$(node -v 2>/dev/null || true)"
  fi
  if [[ -n "${current_node_version}" ]]; then
    echo "web dependencies require Node ${web_node_engine_range}; current node is ${current_node_version}" >&2
  else
    echo "web dependencies require Node ${web_node_engine_range}" >&2
  fi
  echo "Install a compatible Node runtime or provide AGENTTEAM_WEB_NODE_BIN=/path/to/node. Bun is also supported when available." >&2
  exit 1
fi

hash_stream() {
  if command -v sha256sum >/dev/null 2>&1; then
    sha256sum | awk '{print $1}'
    return
  fi

  if command -v shasum >/dev/null 2>&1; then
    shasum -a 256 | awk '{print $1}'
    return
  fi

  if command -v openssl >/dev/null 2>&1; then
    openssl dgst -sha256 -r | awk '{print $1}'
    return
  fi

  echo "sha256 tool is required to prepare web dependencies" >&2
  exit 1
}

lock_hash="$(
  {
    cat "${package_json}"
    cat "${package_lock}"
    printf '%s\n' "${package_manager}"
    if [[ -f "${node_version_file}" ]]; then
      cat "${node_version_file}"
    fi
  } | hash_stream
)"

rolldown_native_binding_spec() {
  node -e '
const fs = require("fs");
const path = "node_modules/rolldown/package.json";
if (!fs.existsSync(path)) process.exit(1);
const pkg = JSON.parse(fs.readFileSync(path, "utf8"));
const optional = pkg.optionalDependencies || {};
const platform = process.platform;
const arch = process.arch;
const linuxLibc = () => {
  const report = process.report && process.report.getReport && process.report.getReport();
  return report && report.header && report.header.glibcVersionRuntime ? "gnu" : "musl";
};
let candidates = [];
if (platform === "darwin" && (arch === "x64" || arch === "arm64")) {
  candidates = [`@rolldown/binding-darwin-${arch}`];
} else if (platform === "linux") {
  const libc = linuxLibc();
  if (arch === "x64" || arch === "arm64") candidates = [`@rolldown/binding-linux-${arch}-${libc}`];
  else if (arch === "arm") candidates = ["@rolldown/binding-linux-arm-gnueabihf"];
  else if (arch === "ppc64") candidates = ["@rolldown/binding-linux-ppc64-gnu"];
  else if (arch === "s390x") candidates = ["@rolldown/binding-linux-s390x-gnu"];
} else if (platform === "win32" && (arch === "x64" || arch === "arm64")) {
  candidates = [`@rolldown/binding-win32-${arch}-msvc`];
} else if (platform === "freebsd" && arch === "x64") {
  candidates = ["@rolldown/binding-freebsd-x64"];
} else if (platform === "android" && arch === "arm64") {
  candidates = ["@rolldown/binding-android-arm64"];
} else if (platform === "openharmony" && arch === "arm64") {
  candidates = ["@rolldown/binding-openharmony-arm64"];
}
for (const name of candidates) {
  if (optional[name]) {
    console.log(`${name}@${optional[name]}`);
    process.exit(0);
  }
}
process.exit(1);
'
}

repair_installed_dependencies() {
  if ! command -v npm >/dev/null 2>&1 || ! command -v node >/dev/null 2>&1; then
    return 1
  fi
  local binding_spec
  binding_spec="$(rolldown_native_binding_spec 2>/dev/null || true)"
  if [[ -z "${binding_spec}" ]]; then
    return 1
  fi
  echo "Installing missing Vite native dependency ${binding_spec}"
  local package_name pack_dir package_tar target_dir
  package_name="${binding_spec%@*}"
  pack_dir="$(mktemp -d "${TMPDIR:-/tmp}/agentteam-web-binding.XXXXXX")"
  npm pack --silent --pack-destination "${pack_dir}" "${binding_spec}" >/dev/null
  package_tar="$(find "${pack_dir}" -maxdepth 1 -type f -name '*.tgz' -print -quit)"
  if [[ -z "${package_tar}" ]]; then
    echo "npm pack did not produce a tarball for ${binding_spec}" >&2
    rm -rf "${pack_dir}"
    return 1
  fi
  target_dir="node_modules/${package_name}"
  rm -rf "${target_dir}"
  mkdir -p "$(dirname "${target_dir}")" "${target_dir}"
  tar -xzf "${package_tar}" -C "${target_dir}" --strip-components=1
  rm -rf "${pack_dir}"
}

ensure_installed_dependencies_valid() {
  if validate_installed_dependencies; then
    return 0
  fi
  repair_installed_dependencies || true
  validate_installed_dependencies
}

refresh_local_node_modules() {
  echo "Refreshing existing web/node_modules directory at ${link_path}; installed dependencies are stale or incomplete"
  (
    cd "${web_dir}"
    install_web_dependencies
    if ! ensure_installed_dependencies_valid; then
      echo "Installed web dependencies are incomplete after refresh" >&2
      exit 1
    fi
  )
  printf '%s\n' "${lock_hash}" > "${local_hash_marker}"
}

validate_local_node_modules() {
  (
    cd "${web_dir}"
    ensure_installed_dependencies_valid
  )
}

validate_shared_node_modules_cache() {
  (
    cd "${env_dir}"
    ensure_installed_dependencies_valid
  )
}

print_installed_dependency_validation_diagnostics() {
  echo "Dependency validation diagnostics:" >&2
  echo "  cwd: $(pwd -P)" >&2
  if command -v node >/dev/null 2>&1; then
    echo "  node: $(command -v node) $(node -v 2>/dev/null || true)" >&2
    node -e "console.log('  node runtime: ' + process.platform + '-' + process.arch)" >&2 || true
  else
    echo "  node: not found" >&2
  fi
  if command -v bun >/dev/null 2>&1; then
    echo "  bun: $(command -v bun) $(bun --version 2>/dev/null || true)" >&2
    bun --bun -e "console.log('  bun runtime: ' + process.platform + '-' + process.arch)" >&2 || true
  else
    echo "  bun: not found" >&2
  fi
  if [[ -d node_modules/@rolldown ]]; then
    find node_modules/@rolldown -maxdepth 1 -mindepth 1 -type d -print >&2 || true
  fi
}

if [[ -d "${link_path}" && ! -L "${link_path}" ]]; then
  stored_hash=""
  if [[ -f "${local_hash_marker}" ]]; then
    stored_hash="$(cat "${local_hash_marker}" 2>/dev/null || true)"
  fi

  if [[ "${stored_hash}" == "${lock_hash}" ]]; then
    if [[ "${AGENTTEAM_WEB_NODE_MODULES_SKIP_VALIDATION:-}" == "1" ]]; then
      echo "Using existing web/node_modules directory at ${link_path}; dependency hash matches (validation skipped)"
      exit 0
    fi
    if validate_local_node_modules; then
      echo "Using existing web/node_modules directory at ${link_path}; dependency hash matches"
      exit 0
    fi
  fi

  refresh_local_node_modules
  exit 0
fi

cache_root="${XDG_CACHE_HOME:-${HOME}/.cache}/agentteam/web-node-modules"
env_dir="${cache_root}/${runtime_version}-${lock_hash}"
env_node_modules="${env_dir}/node_modules"
env_ready="${env_dir}/.ready"
lock_dir="${env_dir}.lock"
lock_owner_file="${lock_dir}/owner.pid"
lock_wait_timeout_seconds="${AGENTTEAM_WEB_NODE_MODULES_LOCK_WAIT_TIMEOUT_SECONDS:-600}"
ownerless_lock_grace_seconds="${AGENTTEAM_WEB_NODE_MODULES_OWNERLESS_LOCK_GRACE_SECONDS:-5}"

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

if [[ -L "${link_path}" ]] && [[ "$(readlink "${link_path}")" == "${env_node_modules}" ]] && [[ -f "${env_ready}" ]]; then
  if [[ "${AGENTTEAM_WEB_NODE_MODULES_SKIP_VALIDATION:-}" == "1" ]]; then
    echo "Using shared web node_modules cache at ${env_dir} (validation skipped)"
    exit 0
  fi
  if validate_local_node_modules; then
    echo "Using shared web node_modules cache at ${env_dir}"
    exit 0
  fi
  echo "Shared web node_modules cache at ${env_dir} is stale or incomplete; rebuilding"
  rm -f "${env_ready}"
fi

mkdir -p "${cache_root}"

lock_acquired="false"
cleanup() {
  if [[ "${lock_acquired}" == "true" ]]; then
    rm -rf "${lock_dir}"
  fi
}
trap 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 node_modules cache lock at ${lock_dir}" >&2
    echo "Check write permissions for ${cache_root}, or set XDG_CACHE_HOME to a writable directory." >&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 node_modules 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 node_modules cache lock at ${lock_dir}"
    rm -rf "${lock_dir}"
    continue
  fi
  if [[ -f "${env_ready}" ]]; then
    echo "Reusing shared web node_modules cache at ${env_dir}"
    break
  fi
  if (( lock_wait_now - lock_wait_started_at >= lock_wait_timeout_seconds )); then
    lock_owner="unknown"
    if [[ -f "${lock_owner_file}" ]]; then
      lock_owner="$(cat "${lock_owner_file}" 2>/dev/null || echo unknown)"
    fi
    echo "Timed out after ${lock_wait_timeout_seconds}s waiting for shared web node_modules cache at ${env_dir}" >&2
    echo "Lock: ${lock_dir}; owner pid: ${lock_owner}; ready marker: ${env_ready}" >&2
    echo "If the owner process is gone, remove the lock directory and retry." >&2
    exit 1
  fi
  if [[ -f "${lock_owner_file}" ]]; then
    echo "Waiting for shared web node_modules cache at ${env_dir} (held by pid $(cat "${lock_owner_file}" 2>/dev/null || echo unknown))"
  else
    echo "Waiting for shared web node_modules cache at ${env_dir}"
  fi
  sleep 1
done

if [[ "${lock_acquired}" == "true" && -f "${env_ready}" ]]; then
  if ! validate_shared_node_modules_cache; then
    echo "Shared web node_modules cache at ${env_dir} is stale or incomplete; rebuilding"
    rm -f "${env_ready}"
  fi
fi

if [[ ! -f "${env_ready}" ]]; then
  tmp_dir="${env_dir}.tmp.$$"
  rm -rf "${tmp_dir}"
  mkdir -p "${tmp_dir}"
  cp "${package_json}" "${tmp_dir}/package.json"
  cp "${package_lock}" "${tmp_dir}/package-lock.json"
  if [[ -f "${node_version_file}" ]]; then
    cp "${node_version_file}" "${tmp_dir}/.node-version"
  fi

  echo "Bootstrapping shared web node_modules cache at ${env_dir}"
  (
    cd "${tmp_dir}"
    install_web_dependencies
    if ! ensure_installed_dependencies_valid; then
      echo "Installed shared web node_modules cache at ${tmp_dir} is incomplete" >&2
      print_installed_dependency_validation_diagnostics
      exit 1
    fi
  )
  touch "${tmp_dir}/.ready"
  rm -rf "${env_dir}"
  mv "${tmp_dir}" "${env_dir}"
fi

rm -rf "${link_path}"
ln -s "${env_node_modules}" "${link_path}"
echo "Linked ${link_path} -> ${env_node_modules}"
