#!/bin/sh
# install.sh — generated by forjar dist (do not edit)
# Rust-native Infrastructure as Code
# Usage: curl -sSf https://raw.githubusercontent.com/paiml/forjar/main/install.sh | sh
# Pinned: curl -sSf https://raw.githubusercontent.com/paiml/forjar/main/install.sh | sh -s -- --version v1.0.0
set -eu

BINARY="forjar"
REPO="paiml/forjar"
INSTALL_DIR="/usr/local/bin"
FALLBACK_DIR="$HOME/.local/bin"
TAG=""
FORCE=0
YES=0
PREFIX=""

# ── Output helpers ──

RED='' GREEN='' YELLOW='' BOLD='' RESET=''
if [ -t 1 ]; then
  RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[0;33m'
  BOLD='\033[1m'; RESET='\033[0m'
fi

info()  { printf '%s%s%s %s\n' "$GREEN" "info:" "$RESET" "$1"; }
warn()  { printf '%s%s%s %s\n' "$YELLOW" "warn:" "$RESET" "$1" >&2; }
die()   { printf '%s%s%s %s\n' "$RED" "error:" "$RESET" "$1" >&2; exit 1; }

usage() {
  cat <<USAGE
Install forjar

USAGE:
    sh install.sh
    sh install.sh --version v1.2.3
    (download first: curl -sSfO https://raw.githubusercontent.com/paiml/forjar/main/install.sh)

OPTIONS:
    --version <TAG>   Install a specific version (e.g., v1.0.0)
    --prefix <DIR>    Install to a custom directory
    --force           Overwrite existing binary
    --yes, -y         Non-interactive mode
    --help, -h        Show this help
USAGE
}

# ── Argument parsing ──

while [ $# -gt 0 ]; do
  case "$1" in
    --version) TAG="$2"; shift 2 ;;
    --prefix)  PREFIX="$2"; shift 2 ;;
    --force)   FORCE=1; shift ;;
    --yes|-y)  YES=1; shift ;;
    --help|-h) usage; exit 0 ;;
    *) die "unknown option: $1" ;;
  esac
done

# Refuse traversal sequences in user-supplied install paths
case "$PREFIX" in
  *..*) die "refusing --prefix containing '..'" ;;
esac

# ── Platform detection ──

detect_os() {
  case "$(uname -s)" in
    Linux*)  echo "linux" ;;
    Darwin*) echo "darwin" ;;
    *)       die "unsupported OS: $(uname -s)" ;;
  esac
}

detect_arch() {
  case "$(uname -m)" in
    x86_64|amd64)       echo "x86_64" ;;
    aarch64|arm64)      echo "aarch64" ;;
    *)                  die "unsupported architecture: $(uname -m)" ;;
  esac
}

detect_libc() {
  if [ "$(detect_os)" != "linux" ]; then
    echo "none"
    return
  fi
  if ldd --version 2>&1 | grep -qi musl; then
    echo "musl"
  elif command -v ldd >/dev/null 2>&1; then
    echo "gnu"
  else
    echo "musl"
  fi
}

# ── Download helpers ──

download() {
  if command -v curl >/dev/null 2>&1; then
    curl -fsSL "$1"
  elif command -v wget >/dev/null 2>&1; then
    wget -qO- "$1"
  else
    die "curl or wget required"
  fi
}

download_file() {
  if command -v curl >/dev/null 2>&1; then
    curl -fsSL -o "$2" "$1"
  elif command -v wget >/dev/null 2>&1; then
    wget -q -O "$2" "$1"
  else
    die "curl or wget required"
  fi
}

compute_checksum() {
  if command -v sha256sum >/dev/null 2>&1; then
    sha256sum "$1" | awk '{print $1}'
  elif command -v shasum >/dev/null 2>&1; then
    shasum -a 256 "$1" | awk '{print $1}'
  else
    warn "no sha256sum or shasum found -- skipping checksum"
    echo ""
  fi
}

# _fj_install_bin <src> <dest> [runner]
#
# Replace <dest> with <src> ATOMICALLY: stage a sibling, then rename(2).
# rename() neither opens the destination (so a RUNNING binary is fine) nor
# follows it (so a DANGLING SYMLINK is replaced, not chased), and leaves no
# window in which the path does not exist.
#
# [runner] is the command runner: `command` (the default) or `sudo`.
# The destination directory must already exist -- every caller creates it.
_fj_install_bin() {
  _fji_src="$1"
  _fji_dst="$2"
  _fji_run="${3:-command}"
  case "$_fji_dst" in
    */*) _fji_dir="${_fji_dst%/*}" ;;
    *)   _fji_dir="." ;;
  esac
  # Staged as a SIBLING so the rename cannot cross a filesystem and degrade
  # into copy-then-unlink. The name is predictable, so unlink before writing:
  # cp must not follow a symlink someone left at that path.
  _fji_tmp="$_fji_dir/.forjar-install.$$"
  "$_fji_run" rm -f "$_fji_tmp" || return 1
  if ! "$_fji_run" cp -f "$_fji_src" "$_fji_tmp"; then
    "$_fji_run" rm -f "$_fji_tmp"
    return 1
  fi
  if ! "$_fji_run" chmod 755 "$_fji_tmp"; then
    "$_fji_run" rm -f "$_fji_tmp"
    return 1
  fi
  if ! "$_fji_run" mv -f "$_fji_tmp" "$_fji_dst"; then
    "$_fji_run" rm -f "$_fji_tmp"
    return 1
  fi
}

verify_checksum() {
  SUMS_URL="https://github.com/${REPO}/releases/download/${TAG}/SHA256SUMS"
  info "downloading checksums..."
  CHECKSUMS=$(download "$SUMS_URL" 2>/dev/null) || CHECKSUMS=""
  if [ -z "$CHECKSUMS" ]; then
    # Fall back to the per-asset .sha256 the release workflow always uploads
    CHECKSUMS=$(download "https://github.com/${REPO}/releases/download/${TAG}/${ASSET}.sha256" 2>/dev/null) || CHECKSUMS=""
  fi
  [ -n "$CHECKSUMS" ] || die "failed to download checksums for $TAG"
  EXPECTED=$(echo "$CHECKSUMS" | grep "$ASSET" | awk '{print $1}')
  if [ -z "$EXPECTED" ]; then
    # A CHECKSUM FILE THAT DOES NOT MENTION THIS ASSET IS NOT A PASS.
    #
    # This used to `warn ... skipping verification` and INSTALL ANYWAY. The
    # dangerous case is not a missing SHA256SUMS — that path already dies above.
    # It is a STALE one: it downloads fine, so the per-asset fallback never
    # fires, and the grep simply finds nothing.
    #
    # forjar v1.18.0 came within one asset of this. Its SHA256SUMS was written
    # by a run that globbed a reused staging directory, so it carried four
    # entries for 1.17.0 alongside 1.18.0's. Had the macOS archives been
    # uploaded by the later run instead of the earlier one, every mac install
    # would have printed a warning and proceeded unverified.
    #
    # Try the per-asset sidecar before giving up, then refuse.
    CHECKSUMS=$(download "https://github.com/${REPO}/releases/download/${TAG}/${ASSET}.sha256" 2>/dev/null) || CHECKSUMS=""
    EXPECTED=$(echo "$CHECKSUMS" | grep "$ASSET" | awk '{print $1}')
  fi
  if [ -z "$EXPECTED" ]; then
    # Wording note: do NOT write "for $ASSET" here. bashrs's SC1086 parses that
    # literal sequence inside a string as a for-loop over an expanded variable
    # and fails the dist lint gate, which is a required check.
    die "$TAG publishes no checksum matching asset $ASSET -- refusing to install unverified"
  fi
  ACTUAL=$(compute_checksum "$ARCHIVE")
  if [ "$ACTUAL" != "$EXPECTED" ]; then
    die "checksum mismatch: expected $EXPECTED, got $ACTUAL"
  fi
  info "checksum verified"
}

# ── Version resolution ──

resolve_version() {
  if [ -n "$TAG" ]; then
    return
  fi
  info "resolving latest version..."
  TAG=$(download "https://api.github.com/repos/${REPO}/releases/latest" \
    | grep '"tag_name"' | head -1 | cut -d'"' -f4) \
    || die "failed to resolve latest version"
  if [ -z "$TAG" ]; then
    die "could not determine latest version"
  fi
  info "latest version: $TAG"
}

# ── Asset resolution ──

resolve_asset() {
  OS=$(detect_os)
  ARCH=$(detect_arch)
  LIBC=$(detect_libc)
  ASSET=""

  case "$OS/$ARCH" in
    linux/x86_64)
      if [ "$LIBC" = "gnu" ]; then
        ASSET="forjar-{version}-x86_64-unknown-linux-gnu.tar.gz"
      fi
      if [ "$LIBC" = "musl" ]; then
        ASSET="forjar-{version}-x86_64-unknown-linux-musl.tar.gz"
      fi
      [ -z "$ASSET" ] && ASSET="forjar-{version}-x86_64-unknown-linux-gnu.tar.gz"
      ;;
    linux/aarch64)
      if [ "$LIBC" = "gnu" ]; then
        ASSET="forjar-{version}-aarch64-unknown-linux-gnu.tar.gz"
      fi
      if [ "$LIBC" = "musl" ]; then
        ASSET="forjar-{version}-aarch64-unknown-linux-musl.tar.gz"
      fi
      [ -z "$ASSET" ] && ASSET="forjar-{version}-aarch64-unknown-linux-gnu.tar.gz"
      ;;
    darwin/x86_64)
      ASSET="forjar-{version}-x86_64-apple-darwin.tar.gz"
      ;;
    darwin/aarch64)
      ASSET="forjar-{version}-aarch64-apple-darwin.tar.gz"
      ;;
    *) die "no pre-built binary for $OS/$ARCH" ;;
  esac

  if [ -z "$ASSET" ]; then
    die "no matching asset for $OS/$ARCH (libc=$LIBC)"
  fi

  # Expand {version} placeholder
  VERSION_NUM="${TAG#v}"
  ASSET=$(echo "$ASSET" | sed "s/{version}/$VERSION_NUM/g")
}

post_install() {
  echo "forjar installed successfully"
echo "Run 'forjar init' to get started"
}

# ── Main ──

main() {
  resolve_version
  resolve_asset

  ASSET_URL="https://github.com/${REPO}/releases/download/${TAG}/${ASSET}"
  TMPDIR=$(mktemp -d)
  ARCHIVE="$TMPDIR/$ASSET"
  trap 'rm -rf "$TMPDIR"' EXIT

  info "downloading $BINARY $TAG..."
  download_file "$ASSET_URL" "$ARCHIVE" || die "download failed: $ASSET_URL"

  verify_checksum

  info "extracting..."
  tar xzf "$ARCHIVE" -C "$TMPDIR" || die "extraction failed"

  # Archives contain a directory named after the asset; fall back to a
  # flat layout for older releases.
  SRC="$TMPDIR/${ASSET%.tar.gz}/$BINARY"
  [ -f "$SRC" ] || SRC="$TMPDIR/$BINARY"
  [ -f "$SRC" ] || die "binary not found in archive"

  # Determine install location
  DEST="${PREFIX:-$INSTALL_DIR}"
  if [ ! -w "$DEST" ] 2>/dev/null; then
    if [ -w "$FALLBACK_DIR" ] || install -d "$FALLBACK_DIR" 2>/dev/null; then
      DEST="$FALLBACK_DIR"
      warn "$INSTALL_DIR not writable, installing to $DEST"
    else
      # Try with sudo
      info "$INSTALL_DIR not writable, using sudo..."
      sudo install -d "$DEST" 2>/dev/null || die "cannot create $DEST"
      _fj_install_bin "$SRC" "$DEST/$BINARY" sudo || die "install failed"
      info "installed $BINARY to $DEST/$BINARY"
      post_install
  info "verifying install..."
  if "$DEST/$BINARY" --version >/dev/null 2>&1; then
    info "$("$DEST/$BINARY" --version)"
  else
    warn "version check failed -- installed binary did not run"
  fi
      return
    fi
  fi

  # Check existing binary
  if [ -f "$DEST/$BINARY" ] && [ "$FORCE" = "0" ]; then
    warn "$DEST/$BINARY already exists -- use --force to overwrite"
    return 1
  fi

  install -d "$DEST" 2>/dev/null || true
  # ATOMIC: `curl | sh` is how a tool is UPGRADED, so the destination is
  # routinely the binary the user just ran. `cp` opens it in place and takes
  # ETXTBSY ("Text file busy"); rename(2) does not. See core::shell_install.
  _fj_install_bin "$SRC" "$DEST/$BINARY" || die "install failed"
  info "installed $BINARY to $DEST/$BINARY"

  post_install
  info "verifying install..."
  if "$DEST/$BINARY" --version >/dev/null 2>&1; then
    info "$("$DEST/$BINARY" --version)"
  else
    warn "version check failed -- installed binary did not run"
  fi

  # PATH hint
  case ":$PATH:" in
    *":$DEST:"*) ;;
    *) warn "add $DEST to your PATH: export PATH=\"$DEST:\$PATH\"" ;;
  esac
}

main
