#!/usr/bin/env bash

set -eo pipefail

REPO_ROOT="$(pwd)"
LOCK_FILE=""
TEMP_DIR=""
SKILLS_CLI_VERSION="1.5.6"
COMMON_SKILLS_SOURCE="warpdotdev/common-skills"
SKILLS_CLI_AGENT="warp"
COMMON_SKILL_SELECTOR="*"

usage() {
  cat <<EOF
Usage: scripts/update_common_skills_lock [options]

Regenerate an existing skills-lock.json from warpdotdev/common-skills.

Options:
  --repo-root <path>   Repository containing skills-lock.json.
  -h, --help           Show this help message.
EOF
}

cleanup() {
  if [[ -n "${TEMP_DIR}" ]]; then
    rm -rf "${TEMP_DIR}"
  fi
}

validate_required_commands() {
  local missing=0
  local tool=""

  for tool in "$@"; do
    if ! command -v "${tool}" >/dev/null 2>&1; then
      echo "error: required command '${tool}' is not installed or not on PATH." >&2
      missing=1
    fi
  done

  if [[ "${missing}" -eq 1 ]]; then
    exit 1
  fi
}

while [[ $# -gt 0 ]]; do
  case "$1" in
    --repo-root)
      if [[ $# -lt 2 ]]; then
        echo "error: --repo-root requires a path." >&2
        usage >&2
        exit 1
      fi
      REPO_ROOT="$2"
      shift 2
      ;;
    -h|--help)
      usage
      exit 0
      ;;
    *)
      echo "error: unrecognized argument: $1" >&2
      usage >&2
      exit 1
      ;;
  esac
done

validate_required_commands git node npm npx

if ! REPO_ROOT="$(cd "${REPO_ROOT}" && pwd)"; then
  echo "error: repository root does not exist: ${REPO_ROOT}" >&2
  exit 1
fi

LOCK_FILE="${REPO_ROOT}/skills-lock.json"
if [[ ! -f "${LOCK_FILE}" ]]; then
  echo "error: missing skills lock file: ${LOCK_FILE}" >&2
  exit 1
fi

trap cleanup EXIT
TEMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/common-skills-lock.XXXXXX")"
CANDIDATE_LOCK="${TEMP_DIR}/skills-lock.json"
git -C "${TEMP_DIR}" init --quiet

# Regenerate in a clean directory: do not seed it with the existing lock.
# skills add merges into whatever lock it finds and never removes an entry
# whose skill is no longer present in ${COMMON_SKILLS_SOURCE}, so seeding
# from the existing lock is what let a rename or deletion upstream leave a
# stale entry behind, pointing at a skillPath that no longer exists. A
# clean directory has nothing to merge stale entries from: skills add can
# only ever emit entries for skills it actually finds in the source.
if ! (
  cd "${TEMP_DIR}"
  npx --yes "skills@${SKILLS_CLI_VERSION}" add "${COMMON_SKILLS_SOURCE}" -a "${SKILLS_CLI_AGENT}" -s "${COMMON_SKILL_SELECTOR}" -y --copy
) < /dev/null; then
  echo "error: failed to generate an updated skills-lock.json from ${COMMON_SKILLS_SOURCE}." >&2
  exit 1
fi

if [[ ! -f "${CANDIDATE_LOCK}" ]]; then
  echo "error: common skills update did not produce a candidate skills-lock.json." >&2
  exit 1
fi

# Merge the freshly regenerated ${COMMON_SKILLS_SOURCE} entries with any
# entries from other sources already in ${LOCK_FILE}; those are never
# touched by the command above and are carried over unmodified. Fail
# closed, rather than silently pruning, if the source produced no entries
# at all while the existing lock has some: that is a sign this run didn't
# actually discover or copy anything, not that every skill vanished
# upstream, and writing that result would delete every locked skill.
if ! node - "${LOCK_FILE}" "${CANDIDATE_LOCK}" "${COMMON_SKILLS_SOURCE}" <<'NODE'
const fs = require("fs");

const existingLockFile = process.argv[2];
const candidateLockFile = process.argv[3];
const source = process.argv[4];

const existing = JSON.parse(fs.readFileSync(existingLockFile, "utf8"));
const candidate = JSON.parse(fs.readFileSync(candidateLockFile, "utf8"));
const existingSkills = existing.skills || {};
const candidateSkills = candidate.skills || {};

const existingSourceCount = Object.values(existingSkills).filter((entry) => entry.source === source).length;
const freshSourceEntries = Object.entries(candidateSkills).filter(([, entry]) => entry.source === source);

if (existingSourceCount > 0 && freshSourceEntries.length === 0) {
  console.error(`error: regenerating from ${source} produced no entries, but the existing lock has ${existingSourceCount}; refusing to prune all of them.`);
  process.exit(1);
}

const merged = {};
for (const [name, entry] of Object.entries(existingSkills)) {
  if (entry.source !== source) {
    merged[name] = entry;
  }
}
for (const [name, entry] of freshSourceEntries) {
  merged[name] = entry;
}

const sortedSkills = {};
for (const name of Object.keys(merged).sort()) {
  sortedSkills[name] = merged[name];
}

const result = { ...candidate, skills: sortedSkills };
fs.writeFileSync(candidateLockFile, JSON.stringify(result, null, 2) + "\n");
NODE
then
  echo "error: failed to merge the regenerated ${COMMON_SKILLS_SOURCE} entries into skills-lock.json." >&2
  exit 1
fi

if cmp -s "${LOCK_FILE}" "${CANDIDATE_LOCK}"; then
  echo "skills-lock.json is already up to date."
  exit 0
fi

cp "${CANDIDATE_LOCK}" "${LOCK_FILE}"
echo "Updated ${LOCK_FILE}."
