#!/bin/sh
# bldpkg: build a PKGBUILD-style recipe into a pacmin package (.pkg.tar.zst).
# one file, POSIX sh (busybox sh clean). the owencore counterpart of makepkg.
#
# usage: bldpkg [-k] [-O outdir] <recipe|dir>
#   -k          keep build tree on failure
#   -O outdir   where to place the package (default cwd)
#
# recipe = PKGBUILD subset: pkgname pkgver pkgrel pkgbase pkgdesc arch url
# license source makedepends depends optdepends provides conflicts replaces
# functions: prepare() build() package().
# recipes see: $srcdir $pkgdir $TCC $CC $AR $CFLAGS, msg() die().
set -u

OUT=.
KEEP=no
RECIPE=

msg() { printf 'bldpkg:> %s\n' "$*"; }
die() { printf 'bldpkg: %s\n' "$*" >&2; exit 1; }
usage() { sed -n '2,7p' "$0" >&2; exit 2; }

while [ $# -gt 0 ]; do
  case "$1" in
    -k|--keep)   KEEP=yes ;;
    -O|--outdir) [ -n "${2:-}" ] || usage; OUT=$2 ;;
    -h|--help)   usage ;;
    -*)          die "unknown flag $1" ;;
    *)           RECIPE=$1 ;;
  esac
  shift
done
[ -n "$RECIPE" ] || usage
[ -d "$RECIPE" ] && RECIPE=$RECIPE/PKGBUILD
[ -f "$RECIPE" ] || die "no recipe: $RECIPE"
command -v tar  >/dev/null 2>&1 || die "tar missing"
command -v zstd >/dev/null 2>&1 || die "zstd missing"

BASE=$(mktemp -d "${TMPDIR:-/tmp}/bldpkg-XXXXXX") || die "mktemp"
srcdir=$BASE/src
pkgdir=$BASE/pkg
log=$BASE/build.log
mkdir -p "$srcdir" "$pkgdir"

cleanup()
{
  rc=$?
  if [ "$KEEP" = yes ]; then
    printf 'bldpkg: kept tree: %s (log: %s)\n' "$BASE" "$log" >&2
  else
    rm -rf "$BASE"
  fi
  exit $rc
}
trap cleanup EXIT INT TERM

# toolchain: tcc first, gcc fallback; recipes may override via env
TCC=${TCC:-}
CC=${CC:-}
command -v tcc >/dev/null 2>&1 && TCC=${TCC:-tcc}
command -v gcc >/dev/null 2>&1 && CC=${CC:-gcc}
[ -n "$TCC" ] || [ -n "$CC" ] || die "no compiler (tcc or gcc)"
AR=${AR:-ar}
CFLAGS=${CFLAGS:--Os -pipe}
LDFLAGS=${LDFLAGS:-}
TCC_BRIDGE=$(CDPATH= cd -- "$(dirname "$0")" && pwd)/cc
export srcdir pkgdir TCC CC AR CFLAGS LDFLAGS TCC_BRIDGE

# ---- fetch + extract sources ----
fetch()
{
  _u=$1
  _dst=$srcdir/${_u##*/}
  [ -s "$_dst" ] && return 0
  case "$_u" in
    file://*)           cp "${_u#file://}" "$_dst" || die "copy $_u" ;;
    http://*|https://*) wget -q --no-check-certificate -O "$_dst" "$_u" 2>/dev/null ||
                        wget -q -O "$_dst" "$_u" || die "fetch $_u" ;;
    git+*)              die "git sources unsupported" ;;
    *)                  die "unsupported url: $_u" ;;
  esac
}

extract()
{
  _a=$srcdir/${1##*/}
  [ -s "$_a" ] || die "not fetched: $_a"
  case "$_a" in
    *.tar.zst|*.tzst) zstd -dc "$_a" | tar -C "$srcdir" -xf - || die "extract $_a" ;;
    *.tar.gz|*.tgz)   tar -C "$srcdir" -zxf "$_a"            || die "extract $_a" ;;
    *.tar.bz2|*.tbz)  tar -C "$srcdir" -jxf "$_a"            || die "extract $_a" ;;
    *.tar.xz|*.txz)   tar -C "$srcdir" -Jxf "$_a"            || die "extract $_a" ;;
    *.tar)            tar -C "$srcdir" -xf  "$_a"            || die "extract $_a" ;;
    *)                cp -r "$_a" "$srcdir"                  ;;
  esac
}

# run a recipe function, log to build.log; opt-steps may be absent
runf()
{
  _f=$1
  _opt=${2:-}
  if ! type "$_f" >/dev/null 2>&1; then
    [ -n "$_opt" ] && return 0
    die "recipe has no $_f()"
  fi
  msg "$_f"
  "$_f" >>"$log" 2>&1 || {
    printf 'bldpkg: %s failed\n' "$_f" >&2
    tail -20 "$log" >&2
    die "$_f (log: $log)"
  }
}

# load recipe metadata first (source urls need vars defined in recipe)
unset -f prepare build package check 2>/dev/null
. "$RECIPE"

: "${pkgname:=$(basename "$(dirname "$RECIPE")")}"
: "${pkgver:=0}"
: "${pkgrel:=1}"
pkgbase=${pkgbase:-$pkgname}
arch=${arch:-x86_64}
builddate=$(date +%s)
license=${license:-}
depends=${depends:-}
optdepends=${optdepends:-}
provides=${provides:-}
conflicts=${conflicts:-}
replaces=${replaces:-}
makedepends=${makedepends:-}
pkgdesc=${pkgdesc:-}
url=${url:-}
source=${source:-}

# fetch + unpack after metadata is known
for _u in $source; do
  msg "source $_u"
  fetch "$_u"
  extract "$_u"
done

# optional: run inside a specific extracted subdir
if [ -n "${build_dir:-}" ] && [ -d "$srcdir/$build_dir" ]; then
  cd "$srcdir/$build_dir"
else
  cd "$srcdir"
fi

runf prepare opt
runf build
runf package
[ -n "$(ls -A "$pkgdir" 2>/dev/null)" ] || die "package() left pkgdir empty"

# ---- .PKGINFO (pacman format) ----
{
  printf 'pkgname = %s\n' "$pkgname"
  printf 'pkgbase = %s\n' "$pkgbase"
  printf 'pkgver = %s\n' "$pkgver"
  printf 'pkgdesc = %s\n' "$pkgdesc"
  printf 'url = %s\n' "$url"
  printf 'builddate = %s\n' "$builddate"
  printf 'packager = owencore builder <builder@owenewans.org>\n'
  printf 'arch = %s\n' "$arch"
  printf 'size = %s\n' "$(du -sb "$pkgdir" | awk '{print $1}')"
  for _l in $license;    do printf 'license = %s\n' "$_l"; done
  for _d in $depends;    do printf 'depend = %s\n' "${_d%%[<>=]*}"; done
  for _o in $optdepends; do printf 'optdepend = %s\n' "$_o"; done
  for _p in $provides;   do printf 'provides = %s\n' "$_p"; done
  for _c in $conflicts;  do printf 'conflict = %s\n' "$_c"; done
  for _r in $replaces;   do printf 'replaces = %s\n' "$_r"; done
  for _m in $makedepends; do printf 'makedepend = %s\n' "$_m"; done
} > "$pkgdir/.PKGINFO"

# ---- tar + zstd ----
LIST=$BASE/paths
{
  printf '.PKGINFO\n'
  (cd "$pkgdir" && find -mindepth 1 ! -name .PKGINFO) | sed 's|^\./||' | LC_ALL=C sort
} > "$LIST"

OUTFILE=$OUT/${pkgname}-${pkgver}-${pkgrel}-${arch}.pkg.tar.zst
tar -C "$pkgdir" --format=gnu --no-recursion --mtime="@$builddate" \
  -cf - -T "$LIST" | zstd -q -c > "$OUTFILE" || die "archive write failed"
[ -s "$OUTFILE" ] || die "archive empty"

size=$(wc -c < "$OUTFILE" | tr -d ' \t')
msg "built $OUTFILE ($size bytes)"
[ "$KEEP" = yes ] && printf 'bldpkg: tree kept: %s (log: %s)\n' "$BASE" "$log"
exit 0
